Changelog

Release notes for the SDK and CLI.

These notes come from the package changelogs in this documentation revision. Compare them with the versions in domino doctor --json, then read the entries between your installed version and the release you plan to use. Follow the upgrade guidance. Versions like 1.0.0-next.N are the v1 prereleases published before 1.0.0, where breaking changes were allowed between prereleases.

Some stable versions in these notes never reached npm. Each is marked Not published to npm and names the first published version that carries its changes; install that one instead. A documentation preview may also include versions not yet published. Confirm that the target release is available on npm before upgrading.

SDK

1.66.1

No changes in this release.

1.66.0

Minor Changes

  • 165ca99: Harder sign-in.

    • domino login codes now expire after 15 minutes, not an hour. Console's approval page shows when the request was made, the country of the IP it came from and the CLI's version and OS, and warns to approve only a code from your own terminal. The CLI sends that description as its User-Agent. One client network (an IPv4 address, or an IPv6 /48) holds at most 20 pending sign-ins.
    • Email sign-in codes name the app. The email says which app and host the code is for, and to enter it only there. Only the latest code for an address works: an earlier one answers code-expired. Ten wrong codes for an address on a project in an hour, or twenty in a day, refuse emailVerify with rate-limited and Retry-After until the window ends, whatever new codes it gets. An address gets at most ten codes an hour from every app together; past that, emailStart answers rate-limited.
    • Plugin OAuth connections finish in Console, as the person who started them (experimental). The vendor's callback no longer connects by itself: it sends the browser to Console with a one-time oauthCode, and the new management.connections.completeAuthorization(plugin, { code }) redeems it within five minutes, for the same person, project and environment only (403 scope-mismatch for anyone else, 409 oauth-flow-expired for a spent or expired code). Without returnTo, the browser returns to Console's Integrations page for the project. authorize now refuses automation, service and hosted app keys with 403 credential-not-allowed. New experimental exports: connectionAuthorizationCompleteInputSchema and ConnectionAuthorizationCompleteInput.

1.65.0

Minor Changes

  • 9a37fe5: Clear test data in hosted app storage. domino storage clear sql DB --table tickets (or --all-tables --keep settings) empties SQL tables and keeps their schema and d1_migrations; domino storage clear kv PHOTOS --prefix photo: (or --all-keys) deletes KV keys. --dry-run counts first, and without --yes the command asks in a terminal and refuses unattended. The management API has the same operation as POST /management/v1/storage/{kind}/{name}/clear, and createManagementClient as storage.clear(kind, name, input). It works only in the test environment: live refuses with test-environment-required. It needs publish, and each clear is audited. A KV clear deletes at most limits.apps.storageClearKeys (10,000) keys a call and answers complete: false while keys remain; the CLI repeats until none do. Test member reset, purge and erase still leave app storage alone.

1.64.0

Minor Changes

  • 0a4d580: Domino now deletes the evidence members submit (photos, text, links and quiz answers) 90 days after the attempt is decided, in test and live, unless you change it. Nothing was deleted when this shipped: evidence that already existed is first deleted 90 days after the change reached Domino, on 18 January 2027 for projects that existed on 20 October 2026, and 90 days after creation for newer projects. Attempts waiting for a decision keep their evidence. Each project's date shows in Console's project settings and in noticeEndsAt.

    To keep evidence longer, or until you delete it, change the period in Console's project settings, with management.attempts.setEvidenceRetention({ evidenceRetention: { days } }) (1 to 3650 days) or { evidenceRetention: "keep" }, or with domino attempts set-evidence-retention --days N or --keep. Changing it needs publish; shortening it, or switching from keep to days, also needs an organization admin or a service key. management.attempts.evidenceRetention() and domino attempts evidence-retention read it. Both methods are experimental. Each change is recorded in the project's access history, and each deletion pass as an evidence-redacted-before event with no actor and no quest. To delete sooner, or per quest, keep using attempts.redactEvidenceBefore.

  • 59cf5db: management.identityIntegrations.createIntoAppSecret(input, name) and addSecretIntoAppSecret(id, name) create an app login key, or add a secret to one, and store the new secret as the hosted app secret name in the key's environment instead of returning it. The response has appSecret and no token. They need publish permission, and like every app login key change they work only from an organization administrator's Console session. When the app secret is refused, no key or secret is created. The CLI skill reference and capabilities describe Console's matching "Also save it as an app secret" option.

  • 0b1f8de: Operators can ban a member, and optionally take back what they earned. While banned, a member's attempts, completions and invitation acceptances are refused with 403 member-banned, their invitation code answers invitation-not-found, attempts waiting for a decision are rejected, tracker records that would count for them are skipped with member-banned, leaderboards and settlements leave them out, and referral payouts to and from them stop. relay.me.get() now answers eligible, false while the member is banned (true from older Relay versions).

    management.members.ban(member, { reason, rollback }) bans, where rollback can revoke all their completions, reverse the referral payouts they received, and reverse manual awards, imported opening balances and engagement payouts, a step at a time in the background. management.members.previewBan(member, { rollback }) previews it per point ledger, and management.members.unban(member, { reason, restoreCompletions }) lifts it. Banning and unbanning need review permission; reversing referral payouts or point awards also needs publish. members.get answers the member's latest ban as ban, and member lists answer bannedAt. The CLI adds domino members ban (with --rollback and --dry-run) and domino members unban. Member bans are experimental. Member imports refuse rows naming a banned member with member-banned.

Patch Changes

  • 166254b: App login now works on a private staging address. createParticipantBackend no longer sends X-Relay-App-Origin with its proxied calls, and Relay selects a request that carries an app login key by the key even when it also names an app origin, so a Test key registered for https://<slug>--staging.domino.page signs members in there without a workaround. Apps on SDK 1.63.0 work without upgrading. The staging-access-required error reference says a request with an app login key needs no staging pass.
  • ff6c976: The editor help for useSession and createParticipantBackend now says what an app with its own login does when it signs members in or out without a page load: call useSession().refresh() after its own sign-in route succeeds and again after its logout route, and sign out through the app rather than useSignOut().
  • 7bd9a79: A refused ID now says what it may hold: an actionId, reference or other ID with a :, . or space is refused with "use 1–100 letters, digits, _ or - (no :, . or spaces)" instead of a regular expression. A management call refused before sending puts each failing field and its reason on one line, such as Invalid management request: actionId: use 1–100 letters, digits, _ or - (no :, . or spaces); kind, code (invalid-input) and cause are unchanged. Relay's own refusal of an invalid request body names the failing fields too, such as Invalid request: actionId, with the same status and code.

1.63.0

Minor Changes

  • 1fa78d2: limits reads Relay's platform limits as number, so a 1.x release can raise one or add one (released SDKs keep checking the values they shipped with, so Relay answers them within those): limits.quests, limits.questTypes, limits.collections, limits.sdkRuntime, limits.memberCompletions, limits.dashboardPanels, limits.dashboards, limits.defer, limits.retryCooldown, limits.analyticsQueryRows, limits.apps (secrets, KV and SQL stores, migrations, storage browsing and schedules), limits.stagedDeployment, limits.capabilityCalls, limits.trackerData, limits.trackerPreview, limits.walletsPerChain, limits.walletEventLogs and limits.walletTransactions. It is exported from @domino-sdk/relay/schema, /authoring, /server and /testing, with its Limits type.

    Deprecated, with unchanged values and types: the literal-typed limit constants limits replaces, which can never change in 1.x. They are questScaleLimits, sdkRuntimeLimits, maxMemberCompletions, dashboardLimits, deferPolicy, retryCooldownPolicy, analyticsQueryLimit, APP_SECRET_LIMIT, APP_KV_LIMIT, APP_SQL_LIMIT, APP_MIGRATION_LIMIT, APP_MIGRATION_FILE_BYTES, APP_MIGRATION_TOTAL_BYTES, APP_STORAGE_ROW_LIMIT, APP_STORAGE_PREVIEW_CHARACTERS, APP_STORAGE_CELL_CHARACTERS, stagedDeploymentLimits, APP_SCHEDULE_LIMIT, APP_SCHEDULE_MIN_INTERVAL, APP_SCHEDULE_TIMEOUT, APP_SCHEDULE_RESULT_BYTES, maxCapabilityCalls, trackerDataMaxBranches, trackerDataMaxEntries, trackerPreviewLimits, WALLETS_PER_CHAIN, maxWalletEventLogs and maxWalletTransactions.

    Deprecated and removed in 2.0.0: Relay's own implementation helpers, which apps and quests have no use for. From @domino-sdk/relay/server: participantRoutes, participantRoutePattern, sequenceCursorSchema, sourceDataSchema, trackerDataIssue, trackerDataIssues, whereSchemaIssue, validateQuestShape, containsReferralIntegrationReward, REFERRAL_INTEGRATION_REWARD_ERROR and nextScheduleRun. From @domino-sdk/relay/schema: dashboardIssue, normalizeDashboard, compatibleSettingKinds, isBoundSetting, whereSettings and sourceContent. They keep working until then.

    z from @domino-sdk/relay/authoring stays zod 4 for all of 1.x.

  • 052744c: inspectIntegrationKey({ baseUrl, integrationKey }) from @domino-sdk/relay/portal-proxy answers which app login key a di_ key is: its id, name, organization, project, environment, origin, identityKind, vouchesAccounts, secretExpiresAt and appOrigins, the addresses Domino serves the project's app from in that environment. It calls the new GET /v1/auth/integration, never returns a secret, and does not count as a use of the key. An unknown, expired or revoked key throws ParticipantExchangeError with 401 invalid-credential. identityIntegrationKeySchema and the IdentityIntegrationKey type are exported from @domino-sdk/relay/server.

    Behavior change: domino secrets set now checks a value that starts with di_ before storing it. It refuses, storing nothing and exiting 2 with INVALID_ARGUMENT, a key that is unknown, expired or revoked, or that belongs to another project or environment, such as a Live key set as Test's secret. It stores a key whose origin is not the app's own address for that environment with a warning, and a key it could not check (network failure, or a Relay without the route) with a warning. A checked key is named in the result's new identityIntegration field. Other values are stored as before.

  • e71245d: createParticipantBackend accepts onExchangeError(error, request), called with each refused sign-in exchange as Domino answered it (a ParticipantExchangeError with status, code, Domino's message and retryAfter) before the browser gets its answer, so the app's server can log why sign-in failed. The browser's answer is unchanged, also when the callback throws.

    Behavior change: creating an app login key for one of Domino's own app addresses in the environment that address does not serve is now refused with 400 invalid-request, and the message names the right environment. A <project-slug>--staging.domino.page or preview address belongs in Test, a live <project-slug>.domino.page address in Live. Other origins, custom domains included, can still use either environment, and existing keys keep working. An origin that is a URL but not an exact HTTPS origin is refused with a message that names the origin it accepts ("Use the exact origin https://app.example.com, without a path, query or trailing slash.") or "Use an HTTPS origin." instead of "Use an exact HTTPS application origin"; over HTTP the management API now answers that message instead of "Invalid management request". The app login guide and the Domino skill references gain a troubleshooting section with the exchange refusal codes.

  • eaaae2a: Test members that reset refuses because they redeemed an offer, hold a reservation or a started reward handover, or took part in a referral can now be purged: management.testMembers.purge({ actionId, members, reason }) and domino test-members purge MEMBER... --reason TEXT delete up to 500 test members in one transaction, in the test environment only. The stock unit each redemption, reservation and pickup handover took goes back to its stock item, so the offer's stock and redeemed count stay consistent, and the members' referrals and referral accounting go with them. List both sides of every referral: one with a member outside the list refuses the call with test-member-reset-blocked (referrals affect another member, naming the unlisted member in outside). Every other reason reset refuses still applies, and a refused purge changes nothing. The audit records one event with the operator, the members, the reason and the stock returned. reset is unchanged. See Purge members reset refuses.

  • 69e017d: Erase a member on the person's request: management.members.erase(member, { reason, actionId? }) and domino members erase MEMBER --reason TEXT [--yes] (POST /management/v1/members/:member/erase, publish permission, live and test). It can't be undone. The member's profile and uploaded avatar, sign-in identities, linked accounts and their tokens, app identities, wallets, sessions, consent, leaderboard aliases, referral code, the evidence of every attempt and the stored content of the tracker records their accounts authored are deleted; reasons people typed on the rows that stay read [erased]. The member ID keeps its points, completions, rewards and referrals. The result is { member, erasedAt, deleted }; while an attempt waits for a decision or a reward handover is in progress it is refused with 409 member-erase-blocked. See Erase a member on request.

    Member reads (members.get, members.list) carry a new erasedAt on an erased member, and members.get answers profile: null for it. After erasure, writes naming the member (points, badges, groups, campaigns, consent, profile changes) are refused with the new 409 code member-erased, records naming its ID attribute to no one, and the same person signing in again becomes a new member. A member import's revert passes over an erased member's row and reports it in keptLinks with reason member-erased. The CLI command asks for confirmation in a terminal and needs --yes otherwise.

  • bbc3623: Klaviyo is the first destination plugin: connected through Klaviyo's OAuth, it keeps opted-in members' Klaviyo profiles current with domino_ properties (points, ranks, quests completed, unredeemed rewards, campaigns), subscribes and unsubscribes them as their email consent changes (a backfill or resync never resubscribes someone Klaviyo has unsubscribed), and records Domino's events as Domino … metrics. It never calls Klaviyo's send, campaign or flow APIs. See the new guide, "Send reminders from Klaviyo".

    management.connections gains, experimental like the rest of it:

    • connect(plugin, { config, dryRun: true }) connects a destination that supports a dry run, in the test environment, without secrets or the vendor's account. It records what it would have sent instead of sending it. Connection.dryRun and PluginInfo.dryRun say which. Refusals: 409 dry-run-not-supported, dry-run-test-only.
    • activity(plugin) reads a destination's delivery record: last sync, failing state, members synced and skipped per reason, the latest backfill or resync with its progress, and the latest 20 errors.
    • resync(plugin) sends every member's profile and attributes again, never past events; one runs at a time.
    • dryRunLog(plugin, { cursor, limit }) pages through a dry run's requests, newest first.

    These answer 409 not-a-destination for other plugins. The CLI adds domino connections connect --dry-run, connections activity, connections resync and connections log.

  • 9cb68e2: A text setting can be secret: settings.text({ label, secret: true }). Operators set its value in Console or with management.quests.updateSettings, and the quest's code reads it as usual, but no management response returns it. Releases leave secret settings out of values and configuration, and list the ones that hold a value in the new optional withheldSettings. So domino deploy --json, --preview --json, quests list, releases get, deployments, attempts and member reads no longer print a shared code a guest must not see. Console shows a secret as Set or Not set, with a field to replace it and Clear. A secret setting takes no default: one with a non-empty default, or a value from a deploy entry's settings, is refused, and reusable quest types refuse secret settings. A secret's entry in a release's fields says secret: true. Settings without secret behave as before. Withholding follows each release's own fields, so releases and deployments from before a setting became secret still show its old value: set a new one after marking an existing setting secret.

  • 8ee88a9: Test members can redeem an offer: management.testMembers.redeem(member, { offer }), with the offer's key or ID, and domino test-members redeem MEMBER OFFER. The redemption runs as a signed-in member's would, with the same price, stock, per-member limit, requirements and refusals, writes the same redemption and entitlement, answers the redemption, and replays by action ID. It works in the test environment only, and the audit names the operator. A test member with a redemption can't be reset.

  • 6fe6f22: The testing kit lists and replays every capability reader, and a few small defaults change.

    • capabilityCalls. runQuest, createTestContext and replayAttempt results gain capabilityCalls: every capability call as { sequence, request, outcome }, with an open CapabilityCallRequest whose kind names the reader. It includes the calls calls left out: Telegram, NFT, contract call, delegation, wallet event and transaction reads, and the member's state. calls is deprecated.
    • Replay divergences of any reader. replayAttempt reports a call of any reader the capture cannot answer in the new capabilityDivergence, and stops there unless facts answers it. Before, such a call of a newer reader failed the replay with TestSetupError; now the replay resolves with decision: null. divergence is deprecated, and is null when the first divergence is a newer reader's call.
    • Tolerant replay. A captured answer with a field or an enum value a later Relay added, such as a new field on a Discord message, now replays instead of failing. Answers in facts are still checked strictly.
    • Reader fakes. fakeWalletReader(facts) and fakeTelegramReader(facts) from @domino-sdk/relay/testing build readers with every method Relay supplies, for tests that assemble their own evaluation context. The readers added after 1.0 stay optional in their types; quest code calls them with a non-null assertion, as the snippets now do (ctx.wallet.nfts!(…), ctx.telegram!.connection!()), instead of guarding against a runtime that lacks them.
    • Behavior change: Telegram trackers fire once per record. automatic() on a telegram.reactions() or telegram.messages() tracker now defaults to on: "new-record" instead of "new-observation", so a reaction removed and added again no longer runs the quest again. Published quests keep the mode they were published with; republishing one with this SDK switches it unless it sets on.
    • domino check warns TRIGGER_REFIRES_ON_REFRESH for a non-content quest on a YouTube Shorts or TikTok tracker that runs on every observation: each refresh evaluates it again for every video. Use content({ tracker }) with performance for a metric threshold.
    • RelayProvider takes followWallet. followWallet={false} keeps a wallet sign-in's session when the wallet switches account or locks. The default, true, signs out as before.
    • Behavior change: unauthenticated ends the session. A participant backend's (createParticipantBackend) 401 unauthenticated now ends the member's session in the SDK's experience, progress and activity observers, as not-signed-in does, instead of reporting an error. RelayErrorCode.Unauthenticated names it.
    • enrollment on not-enrolled skips. Tracker trigger outcomes, relay.quests.outcomes() and automatic outcomes carry an optional enrollment on not-enrolled skips: "quest" for the quest's own enrollment, "campaign" for its campaign's. A campaign skip on a tracker trigger now says "Member has not enrolled in the quest's campaign".
    • Naming. A leaderboard's balance is the ledger it ranks and its points pool's ledger the one the pool pays into; both are documented on the type. New fields that name a ledger are called ledger; existing balance fields keep their name in 1.x.
  • 561f4ab: Tiered rewards (defineTieredReward) can be listed, archived and restored. management.resources.list() and domino resources list list them in a new tieredRewards array (managedTieredRewardSchema), beside offers, and domino tiered-rewards list lists them alone. The experimental management.tieredRewards.archive and unarchive (POST /management/v1/tiered-rewards/REWARD/archive|unarchive) and domino tiered-rewards archive|unarchive KEY --reason archive and restore one in test, by key or ID, with publish permission; a promotion carries it to live, and Console Settings → Resources lists them with the same controls. Each returns { resource, changed }, typed by tieredRewardLifecycleResultSchema. While one is archived, a deploy or promotion that would make a quest, offer or leaderboard prize newly grant it fails with 422 tiered-reward-archived, naming it in reward. What already granted it keeps granting it, its entitlements and handover keep working, and its key stays taken. Console Rewards leaves archived tiered rewards out of its rewards. Promotions now list tiered rewards as definitions of kind tiered-reward, and a rollback archives one the earlier promotion did not carry.

  • 8d79fd6: Four operations that had two public paths with different results now agree, or say how they differ:

    • management.deployments.promote, submit and reject are deprecated, removed in 2.0.0. promote prepares a promotion of all of test and publishes nothing; its JSDoc now says so, and that the answered id is the promotion's, read with promotions.get and rejected by deployments.get and publish. Use management.promotions.prepare() with a client for test, and promotions.reject to decline. submit and reject act only on live deployments made before live refused them. domino deploy --promote is deprecated in favor of domino promote --environment test, and prints a warning naming it.
    • management.testMembers.acceptAnyway(member, { attempt, rules: "live", reason }) applies live attempts.acceptAnyway's rules in test: reason is required, and an archived quest, a superseded attempt, a completion cooldown, an exhausted budget, an existing or revoked completion and a used key are refused with live's 409 codes, leaving the attempt and balances unchanged. The default (rules: "test") still accepts those, with no reason, so a tester can get a test member unstuck. A reason is recorded with the decision either way. domino test-members accept-anyway MEMBER ATTEMPT --live-rules --reason TEXT sends it.
    • GET /v1/reward-deliveries and GET /management/v1/reward-deliveries answer items beside deliveries, with the same rows, so paginate((cursor) => relay.me.rewardDeliveries({ cursor })) and management.rewardDeliveries({ cursor }) read every page. Both clients fill items from deliveries when an older Relay sends only deliveries. useExperience().deliveries reads the role deliveries again on each delivery.updated activity event, so a role an operator revoked shows without waiting for focus.
    • defineLeaderboard prints a warning, and domino check reports a LEADERBOARD_TIES_DIFFER warning, when a board's live ties differs from its settle.ties, including a board that sets only settle.ties: "earliest", whose standings share ranks while its prizes pay first-to-reach. leaderboardTiesWarning(board) from @domino-sdk/relay/authoring returns that message, or null. domino check's diagnostics now include warnings from data definitions beside its errors; only errors fail it.

Patch Changes

  • cc04dae: Browser apps bundle less of the SDK: an app's bundler now drops the schemas it never reads, such as tracker, wallet-read and management schemas, instead of keeping every schema in each module the client imports. @domino-sdk/relay/react adds about 41 KB less minified JavaScript (235 KB instead of 276 KB), and @domino-sdk/relay/browser about 42 KB less. Exports and behavior are unchanged.
  • 4842e30: Wording only. The app identity kind example is now app:game, including the validation message "Name an app identity kind like app:game: …", since a Telegram account is vouched for rather than modelled as an app identity. The JSDoc of progress(), quests.claim, me.claim and the quest interactions says which meaning of "progress" and "claim" each uses, and domino test-members pass help calls progress(...) quests counter quests. Examples use the @XDevelopers X account. The changelogs mark the versions that were never published to npm.
  • 425e4df: Internal: the "HTTPS, or plain HTTP on a loopback host" check for API URLs, sign-in URLs and portal proxy origins now has one implementation, shared through @domino-sdk/relay/internal. Published behavior is unchanged.
  • 8515f3f: participantResponses and capabilityResponses from @domino-sdk/relay/schema now build each reader on first use, as managementResponses and the field-tolerant tables already do. Each reader is built once and stays writable; types and parse results are unchanged.
  • 8278803: Relay now refuses a malformed personal API key ID with 400 invalid-request ("Use the key's ID, such as ak_…") on DELETE /management/v1/tokens/:id and PUT /management/v1/tokens/:id/scope. A key ID is ak_ followed by letters, digits or _. IDs from tokens.list() and domino tokens list already have that form, so no working call changes: an unknown ak_… ID still revokes as a silent success, and a key that belongs to someone else is still refused with 403.
  • eb8572b: The testing kit's participant API answers an activity cursor it cannot read as Relay does: 400 invalid-cursor, "Invalid cursor. Start from the first page.", instead of invalid-request.

1.62.0

Minor Changes

  • a341b16: Analytics counts Relay's own key-used rejection, of an attempt whose completion key another member's completion holds, as its own rejection source. rejectionsBySource gains keyUsed, and analytics.query's source breakdown a keyUsed row; evaluator no longer includes them, while an evaluator's own key-used still counts there. Each topReasons item carries codeSource (relay, evaluator, or null without a code), and Relay's key-used and an evaluator's are separate rows, in topReasons and in the reason breakdown. Both fields are optional in the schema, since older servers don't send them. domino analytics get marks the code of a top reason Relay wrote, such as [relay key-used] or [relay budget-exhausted].

  • bd622ae: An app login's server can vouch for its members' X, Telegram and Discord accounts and wallets when it signs them in. createParticipantBackend's authenticate may return accounts next to subject, and onAccounts receives each account's outcome (attached, already-linked, or refused with a member import refusal code). The new exchangeParticipant from @domino-sdk/relay/portal-proxy runs the same exchange from a server without a browser and throws ParticipantExchangeError, a RequestError with the refusal's status, code and retryAfter in milliseconds (isParticipantExchangeError recognizes it). The key needs Vouches accounts, which management.identityIntegrations.setAccountVouching(id, { enabled, reason }) turns on from a Console session with publish permission; identity integrations report it as vouchesAccounts. Vouched accounts and wallets carry vouchedBy: { integration, integrationName } next to imported: true.

  • 76be180: An attempt carries codeSource: relay when Relay wrote its code, evaluator when the evaluator's decision.reject({ reason, code }) did, and null without a code (AttemptCodeSource, open). reservedAttemptCodes no longer grows, so a quest built with any 1.x SDK keeps working when Relay adds a code: key-used stays usable as an evaluator's reject code, and codeSource tells it apart from Relay's own shared-key rejection. Members still read a rejected attempt's code only when Relay rejected it.

  • 5233be5: The experimental billing results read the plan as BillingPlan (KnownBillingPlan | (string & {})): subscription.plan and scheduledChange.plan in management.billing.read(), cancel(), resume() and organization(), billingSubscriptionSchema, projectBillingSchema and organizationBillingSchema, and the --json results of domino billing show, billing organization, billing cancel and billing resume, so a plan Relay adds no longer fails to parse. Handle a plan name this SDK does not know. The stable management.usage.plan() and domino usage plan are unchanged: the subscription they carry keeps the plans this SDK knows (launch, growth, scale).

  • 3665a4e: Billing is experimental while it is open to Domino staff only: the management.billing methods, the billing schemas and types in @domino-sdk/relay/server, and the domino billing commands may change in a minor release, plan names included. domino billing now appears under Experimental commands. management.billing.checkout() and changePlan() take any plan name (BillingPlan, KnownBillingPlan | (string & {})), and domino billing checkout and billing change take any --plan; Relay refuses a plan it does not offer with 400, as before. management.usage.plan() and domino usage plan stay stable.

    The CLI's JSON result schemas now leave objects open, as documented, so a JSON Schema validator such as Ajv accepts fields a later release adds; only the variants of a oneOf stay closed. webhooks deliveries, storage rows and storage keys results declare the optional nextCursor the API already sent. secrets, storage and schedules results include environment, and these commands warn on stderr when RELAY_ENVIRONMENT or relay.json names live while they use test. contract.json marks deprecated options with deprecated, as it does commands, and capabilities.json no longer lists deprecated commands. management.referrals.control and referralControlSchema now point to pause and resume instead of the deprecated suspend.

  • 361a5b1: A deployment keeps the active release of a quest it doesn't change, instead of publishing an equal one with a new ID. Deployment previews and results carry questChanges: each quest by key, created, updated (with the release it replaces) or unchanged. domino deploy lists the created and updated quests in its confirmation and in the --preview --json summary, so a deploy that would replace a quest you didn't touch shows before it publishes.

  • 154af63: Discord message and reaction records name a thread's parent channel: DiscordMessageData and DiscordReactionData gain parentChannelId, the channel a thread belongs to (a forum post's forum), or null outside a thread. It is absent when it is unknown, because Discord's channel read failed. channelId stays the thread's own ID. A trigger's where: { path: "parentChannelId", equals } selects a channel's threads; to count a channel and its threads, compare both fields in the evaluator. management.testMembers.record and domino test-members record --data take parentChannelId for Discord records.

  • 79b7c68: keyed(fn, { across: "members" }) makes a quest's completion keys unique across members: each key completes the quest once in the project and environment, for whichever member's completion commits first, such as a single-use code or a post that pays once. The release's completion is { kind: "keyed", across: "members" }, and quest progress and experience reads carry it. Another member's attempt with a used key is rejected with key-used and codeSource: "relay", which members see; attempts.acceptAnyway refuses it with 409 key-used while the key is held. Revoking the holder's completion with freeOccurrence: true frees the key. Member imports refuse a row with MemberImportRefusalCode.KeyUsed when another member or an earlier row already uses one of its keys.

  • 56a0601: fakeEvaluationMember(methods) from @domino-sdk/relay/testing builds an EvaluationMember for a test that assembles its own evaluation context. Pass the methods your code reads; the rest answer for a member with no linked accounts, completions, groups or enrollments, and balance and referrals fail with a CapabilityError of code author-error. A member written by hand stops compiling when a minor release adds a method; one from fakeEvaluationMember does not.

    The CLI's automation contract (cli/contract/contract.json) lists the JSON Schemas of relay.json and the checkout's domino.json under files, so a later release is checked to accept every file an older CLI wrote.

  • 5cc0d4d: Revoking a completion with freeOccurrence: true now returns the budget it used. Each quest budget the completion was charged gets that charge back, in the same transaction and in the budget period it was charged to, so the freed occurrence can be completed again within the cap. A point-priced budget gets back its charge less the points the revoke couldn't reverse. CompletionRevocation reports this in a new optional budgets (budget, released). Revoking without freeing the occurrence leaves budgets spent, as before. Completions recorded before this release give no budget back and report budgets: [].

    Restoring such a revocation charges that budget again and reports it in a new optional CompletionRestoration.budgets (budget, charged). The charge is checked against the budget's current limit, or the limit it was first charged under if the quest removed the budget or changed its period or cost. Restore now also refuses with 409 budget-exhausted, naming the budget, when a budget has no room left for the charge, and then changes nothing. This is a behaviour change: until now, budgets stayed spent after any revoke.

  • f9c9f34: defineOffer, defineLeaderboard (prizes) and defineReferral (inviter, invitee and milestone rewards) now throw when given a badge() or groupMembership() reward, so domino check reports it before a deploy. These definitions type-checked but a deployment always refused them; now they fail when defined, with Badge rewards can only be granted by quests or manually, not by offers (or leaderboard prizes, referral programs), or Group membership rewards can only be granted by quests, not by …. A deployment refuses them with the same message and 422 validation-failed, instead of claiming a tier reward was needed. badgeReward and groupReward from @domino-sdk/relay/schema are deprecated: use badge() and groupMembership() from @domino-sdk/relay/authoring. defineOffer from @domino-sdk/relay/schema is deprecated too and keeps its old behaviour; import it from @domino-sdk/relay/authoring, which does the check.

  • dcc32da: ctx.wallet.events() takes newest, from 1 to 1,000: it returns at most that many of the newest matching events, reading back from the newest block, and stops at the 3,000,000-block or 1,000-log cap instead of throwing author-error. WalletEvents gains an optional complete, false when a newest read left older matching events out or stopped before since. A busy wallet, or a long window on a fast chain such as Arbitrum One (3,000,000 blocks is about 9 days there), no longer has to fail the attempt. A call without newest behaves as before: it returns every matching event, now with complete: true, or throws author-error. A read whose start-block estimate overshot 3,000,000 blocks while the span itself fit is no longer refused. The onchain-action snippet reads with newest: maxWalletEventLogs.

  • 98ad384: A member import carries its real state in a new state field: open, checking, checked, applying, applied, stopped, reverting, reverted or canceled, typed open (KnownMemberImportState | (string & {}), with MemberImportState listing the known values), so Relay may add states. status is unchanged and still reads applied for a stopped, reverting or reverted import, so code filtering on status === "applied" counts those too; read state instead. Import rows gain reverted, true once the import's revert went through the row. Both fields are optional in the schema, since older servers don't send them. domino import and domino imports results include state, and the CLI polls on it.

    MemberSummary (relay.me(), useMe()) now types email, emails and clock as required, since Relay always sends them: code no longer needs to check them for undefined. The SDK reads a /v1/me response from a Relay older than these fields with email: null, emails: [] and a clock at real time (offset 0), the same as code treating them as absent did. memberTime() still accepts an object without clock.

    MemberSummary, LeaderboardPage, LeaderboardHistoryPage, Badge, MemberBadge and MemberBadges are marked as response types Relay may add properties to.

  • 4bf82e5: More management writes are safe to retry. memberImports.check, apply, cancel and stop, and promotions.approve, take an optional { actionId } before the call options (check(id, { actionId }); check(id, call) still works), and so does webhooks.replay(id, delivery, { actionId }). promotions.reject, members.unlinkCreatorAccount and identityIntegrations.setAccountVouching take actionId in their input. With an action ID, a retry answers with the first result instead of a refusal for the state the first call left. These methods now generate an action ID when you pass none, as other writes do, so the client retries them after a lost answer.

    Inputs fenced by revision also take it as expectedRevision, the name newer methods use: quest and redemption pause and resume, offers.control, offers.cancelRedemption, handover.begin and cancel, entitlements.void, restore and correctCollection, the pickup policy, members.unlinkCreatorAccount and the deprecated account unlinks. Pass either; a call that names two different revisions is refused. expectedRevision() and the ExpectedRevision type are exported from @domino-sdk/relay/server.

    attempts.acceptAnyway, attempts.redactEvidence, completions.revoke and completions.restore take their path ID first, as the route names it: acceptAnyway(attempt, { reason }). The object form, acceptAnyway({ attempt, reason }), keeps working.

  • 4bf82e5: relay.badges.catalog() reads the project's active badge catalog as { items }, like relay.me.badges(), from the new GET /v1/badges/catalog. relay.badges.list() and GET /v1/badges keep answering the bare array and are deprecated. useBadgeCatalog() now reads the new route and still returns the array. createFakeRelay takes a badges seed and answers both routes from it.

    relay.auth.exchange(), ExchangeInput and exchangeSchema are deprecated: POST /v1/auth/exchange refuses the body they send with 400, because the route takes { subject, accounts?, previousToken? } with an identity integration server key. Sign a user in from a server with exchangeParticipant from @domino-sdk/relay/portal-proxy.

    The activity feed's JSDoc, and paginate()'s, now say that me.activity.list never answers a null nextCursor, so a loop over it stops on its own condition, such as an empty page or a count.

  • 4bf82e5: ISO times in management responses carry their epoch milliseconds beside them, named <field>Ms. analytics.read and analytics.query (and so each dashboards.read panel) send range.fromMs, range.toMs, each bucket's startMs and endMs, previous.fromMs and previous.toMs, and testEnvironment.lastResetAtMs; custom domains (management.domains.list|get|add|recheck) send checkedAtMs, nextCheckAtMs, createdAtMs and cloudflare.certificateExpiresAtMs. The ISO fields are unchanged, and the new fields are optional in the schemas, since older servers don't send them. management.testMembers.advance(member, { actionId, to }) takes to as epoch milliseconds as well as an ISO 8601 instant with an offset; testMemberClockAdvanceSchema parses epoch milliseconds to that instant as an ISO string in UTC, so its parsed to stays a string.

  • ea2c9be: A retention job can delete the evidence of a quest's attempts decided before a cutoff: management.attempts.redactEvidenceBefore({ quest, decidedBefore, reason }) (review permission; in live it also requires an organization administrator's Console session or personal key, or a service key, and refuses any other caller with 403 permission-required, because the deletion can't be undone) deletes up to 100 attempts' evidence per call, oldest decision first, exactly as attempts.redactEvidence does for one, and answers more: true while others remain. Attempts still waiting for a decision are skipped, and a decidedBefore in the future is refused with 400 invalid-request. Each call records one evidence-redacted-before audit event. domino attempts redact-evidence-before --quest QUEST --decided-before MS --reason TEXT repeats the call until nothing is left and prints the total.

  • 2de5d71: @domino-sdk/relay/testing adds createFakeRelay(options), an experimental in-memory participant API for UI tests of apps built on @domino-sdk/relay/react. Seed it with a member (or null for signed out), quests, balances, activity, leaderboards and the rest; it answers the session, logout, experience, points, point history, point ledgers, activity, leaderboard standings and the server integration exchange, under /v1/... and /relay/v1/..., and returns handle(request) for a Playwright or MSW route handler, fetch for createRelayClient, and requests, every request it answered. Each answer is checked against the SDK's response schema, so a seed Relay could never send throws a TestSetupError naming the route. Another participant route answers 501 with code not-faked until routes answers it. The testing guide lists the requests RelayProvider makes on mount and while polling. createFakeRelay and its FakeRelay, FakeRelayOptions and FakeRelayRequest types are tagged @experimental, outside the 1.x compatibility promise: a minor release can change them.

  • e58c2ae: Name a member's account the way a member import does, everywhere an operator command takes one: { kind: "x" | "discord" | "telegram", subject } with the numeric user ID, { kind: "app:<name>", subject }, or { kind: "wallet", chain, address } (identityReferenceSchema, IdentityReference).

    • members.identityStates.unlink and relink take it as identity (memberIdentityStateChangeInputSchema, MemberIdentityStateChangeInput); setSignIn also takes a Cosmos wallet with its network. An X or Discord subject names the member's selected account with that subject, else the only one they hold. A subject the member holds through several issuers, none of them selected, fails with 409 identity-ambiguous; name it by issuer then.
    • members.lookup takes it, and sends it as the provider and subject every Relay version reads.
    • testMembers.linkAccount and unlinkAccount take { actionId, kind, subject } for an X or Discord account.
    • The CLI takes --kind and --subject on domino members lookup (with --chain and --address for a wallet) and on domino test-members link-account and unlink-account.

    Deprecated, removed in 2.0.0: the { kind: "oauth", provider, issuer, subject } and { kind: "app", appKind, subject } input selectors, memberIdentityLinkChangeInputSchema and MemberIdentityLinkChangeInput, members.lookup({ provider, subject }), the test-member { provider, id }, and the CLI's members lookup --provider and test-members link-account|unlink-account --provider|--id. They keep working through 1.x; contract.json marks the deprecated options with deprecated.

    Identity reads gain an optional source: member, import, app-login or test-link, an open string (IdentitySource, KnownIdentitySource), and import, the ID of the member import that attached the identity when Relay recorded it. Identity states of every kind carry them, and so do members.get's wallets and identities and the deprecated oauthAccounts and telegramAccount. imported is unchanged.

  • 3a3feef: A leaderboard can rank its live standings first-to-reach. defineLeaderboard({ ..., ties: "earliest" }) orders equal scores by each member's latest qualifying entry, earliest first, then by member ID, and gives every member who scored their own rank: the order a settlement with settle.ties: "earliest" freezes. Standings pages, me, nearby, placement, and the management and CLI standings reads all follow it, so a tie-broken board is one paged read.

    ties is optional. Omitted or "shared", equal scores share a rank as before, so published boards rank unchanged. It is independent of settle.ties; set both on a board that settles so members see the order prizes follow. Members listed through includeUnscored still share one rank after every member who scored.

  • 7a93f76: createParticipantBackend from @domino-sdk/relay/portal-proxy accepts a loopback HTTP app origin (http://localhost, http://127.0.0.1 or http://[::1], any port), so a browser test can run the real adapter, identity exchange included, at a local address against createFakeRelay's fetch. On such an origin the domino_app_session cookie omits Secure. Any other http: origin still throws, baseUrl must still be HTTPS, and Relay still registers only HTTPS app origins.

  • 1a70098: management.members.lookup({ provider: "app:<name>", subject }) finds the member holding a linked app identity, so a server can map one of its user IDs to the member. members.people(), members.list(), members.lookup() and members.get() list each member's linked appIdentities ({ kind, subject }), and members.list({ q }) also matches part of an app identity's subject. Identities an operator unlinked are neither found nor listed.

  • dd7ced3: Experimental: a member import row can carry the member's marketing consent from the platform they come from. The optional consent list on memberImportRowSchema takes one { channel, state: "granted" | "withdrawn", at, wording? } per channel; applying the row records it with its original time and wording, marked source: "imported", in the row's transaction, unless the member chose on that channel in Relay (their choice always stays) or an earlier import recorded consent as recent or more recent. A check refuses the row with future-action for a time in the future, unsupported-consent-channel for a channel other than email, consent-wording-required, consent-wording-too-long or consent-wording-invalid for the wording, and duplicate-entry for a channel named twice. KnownConsentSource gains imported. The CLI imports reference describes the field.

  • 5a34b36: Experimental: members record their marketing consent per channel. relay.consent.get() lists the signed-in member's current consent, one entry per channel they chose on (member, channel, state, at, wording, source), and relay.consent.set({ channel, granted, wording, actionId? }) grants or withdraws one channel. The useConsent() React hook returns the query with grant(channel, wording) and withdraw(channel, wording?). Relay stamps the time and stores the wording verbatim; a repeat of the current state and wording records nothing. Only email is accepted for now: other channels are refused with unsupported-consent-channel, a grant without wording with consent-wording-required, wording over 2,000 characters with consent-wording-too-long, and wording with a NUL or a lone surrogate with consent-wording-invalid. Channel, state and source are open strings in results. The consent API is @experimental and can change in a minor release. The participant API reference and capabilities.json describe it.

  • 4b7c062: Integration modules, reward kinds and webhook event types now have open types beside SDK 1.x's closed ones, which keep their exact shapes.

    • defineQuest accepts an integration module whose provider the SDK does not know, such as { provider: "future-chat" }. IntegrationModule.provider is IntegrationModuleProvider, KnownIntegrationModuleProvider | (string & {}). The quest builds; its description keeps integrations to the modules the SDK knows and adds integrationModules, every declared module. Publishing a quest that declares a module Relay does not have is refused with 422 unknown-integration-module. Discord and X quests behave as before. Evaluator contexts type readers only for the modules the SDK knows, now named KnownIntegrationModule: EvaluationContext's module parameter takes readonly KnownIntegrationModule[], so a helper that passed readonly IntegrationModule[] to it explicitly passes readonly KnownIntegrationModule[] instead.
    • Release reads (management.releases.get, releases.list, publish results and every other PublishedRelease) add integrationModules and rewardEntries. rewardEntries lists every reward as a RewardEntry, { type, data }, with an open RewardType; a reward kind the SDK does not know, which a later Relay adds, will appear only there and never in rewards. management.quests.previewAutomaticCatchUp() adds the same rewardEntries beside rewards. Older Relay versions omit these fields.
    • ActivityType (KnownActivityType | (string & {})) from @domino-sdk/relay/server names the webhook and activity event types for your own handlers; WebhookEvent.type stays a string. Ignore a type you don't handle: Relay adds event types.
  • de99154: Every sign-in provider Relay has, Telegram included, now has open surfaces beside the closed AuthProvider lists, which keep their exact shapes. SignInProvider is KnownSignInProvider | (string & {}), so handle a provider you do not know.

    • relay.auth.providers() adds signInProviders, [{ provider, configured }] for every sign-in provider. management.participantSignIn() adds the same signInProviders. Older Relay versions omit both.
    • relay.auth.signInAccounts() (GET /v1/auth/sign-in-accounts) returns the member's account on every sign-in provider as SignInAccounts, shaped like AccountConnection with an open provider and status.
    • relay.auth.start({ provider, mode }) and relay.auth.disconnect(provider) take any SignInProvider, such as "telegram".
    • useAccount(provider) from @domino-sdk/relay/react reads one provider's account, with connect(), disconnect() and isConnected.
    • management.members.accounts(member) (GET /members/:member/accounts) returns { accounts }, the member's X, Discord and Telegram selections as ManagedAccounts with an open provider. management.members.unlinkAccount(member, { provider, subject, expectedRevision, reason, actionId? }) (POST /members/:member/accounts/unlink, review permission) unlinks one and returns that provider's selection; a retry with the same actionId returns the first result.
  • 80e09c2: Experimental: operators read a member's marketing consent. management.members.consent(member) lists the member's current consent, one entry per channel they chose on, and management.members.consentHistory(member, { cursor, limit }) pages through every change on every channel, newest first, as { items, nextCursor }; each entry is { member, channel, state, at, wording, source }, and memberConsentHistorySchema and memberConsentHistoryQuerySchema are exported from @domino-sdk/relay/schema. Both need read permission (GET /management/v1/members/:member/consent and /consent/history); a member who never chose reads as empty, and an unknown member fails with 404 not-found. domino members consent MEMBER prints both, with --limit and --cursor for the history. The Console member page shows the same, read-only. There is no management write: only the member grants or withdraws consent. The consent API is @experimental and can change in a minor release.

  • 423b6fc: Plugin connections, experimental. management.connections (list, get, connect, update, disconnect) connects a project to the plugins Domino writes, separately in test and live; a promotion never copies a connection. A connection's config is checked against the plugin's JSON Schema (422 invalid-config names the failing path), its secrets are write-only and sealed at rest, every change takes an actionId, and update and disconnect take an expectedRevision (409 connection-changed). The new exports (connectionSchema, connectionPageSchema, Connection, ConnectionStatus and the connect, update and disconnect inputs) are experimental and may change in a minor release. The CLI adds the experimental domino connections list|get|connect|update|disconnect, test unless --environment live, reading secrets from stdin as JSON or at hidden prompts, never from arguments.

  • 01b789e: Experimental management.plugins.list() answers the plugins a deployment runs, each with the public part of its manifest: name, slots, the config's JSON Schema (configSchema), its secret fields and the Domino event types a destination receives. The new exports (pluginSchema, pluginPageSchema, PluginInfo, PluginPage, PluginSlot, KnownPluginSlot and knownPluginSlots) are experimental and may change in a minor release. Console builds each plugin's connection form on Integrations from it.

  • 2a83792: Plugin OAuth connections, experimental. A plugin whose plugins.list() entry has oauth connects through its vendor's OAuth instead of entered secrets: management.connections.authorize(plugin, { config, returnTo?, actionId? }) answers { authorizationUrl, expiresAt }, a person opens the URL and grants access, and the vendor's callback makes the connection ready. The URL works once, for ten minutes. Domino refreshes the tokens before they expire and never answers them; when the vendor refuses a refresh, the connection is attention with the new reconnectReason (revoked or expired), and authorizing again reconnects it. Disconnecting deletes the tokens, then revokes them at the vendor; a failed revocation is logged. connect refuses an OAuth plugin with 409 oauth-required, authorize refuses one connected with secrets with 409 oauth-not-supported, and a deployment without the vendor's OAuth app with 409 plugin-not-configured (oauth.configured is false). New experimental exports: connectionAuthorizeInputSchema, connectionAuthorizationSchema, ConnectionAuthorizeInput, ConnectionAuthorization, ConnectionReconnectReason, KnownConnectionReconnectReason and knownConnectionReconnectReasons. The CLI adds the experimental domino connections authorize <plugin> --config-json …, which prints the authorization URL.

  • 172c3b5: Experimental: a viewer project role and grant end dates, to share a project's dashboards with a sponsor or client. A viewer holds one permission, dashboards, which reads dashboards.list and dashboards.read and nothing else: no members, evidence, quests or settings. Every other role holds it too, and read keeps reading dashboards, so no existing person or key loses access.

    management.grants.set takes projectGrantSchema: roles may name viewer, and an optional expiresAt (epoch ms, or null for no end) ends the grant's access without anyone removing it. An ended grant stays in grants.list(), and the access history records each end date. Released SDKs keep reading grants: a grant's roles keeps the roles 1.x names, so a viewer's is empty, and the new projectRoles lists every role. Likewise a project in management.access() keeps dashboards out of permissions and lists it in the new projectPermissions. projectRoleSchema, projectPermissionSchema, projectRolePermissions, projectGrantSchema and these fields are experimental.

    Console grants the viewer role and an end date under Settings → Access → Team, and shows an ended grant as Expired. A viewer's Console shows only Analytics, opened on the project's first dashboard. The CLI's agent references describe the role.

  • b2b8127: A full deployment to test warns quests-not-in-bundle with the keys of quests published from code that it leaves out, such as a starter's sample quests whose files were deleted: they stay as published until archived with domino quests archive <key> --reason "…". Archived quests and quests created in Console are not listed. A deployment input with partial: true doesn't warn, and domino deploy --only sends it. domino stage now prints the warnings of the deployment its build published, and its --json result carries them in warnings; a build's status (GET /builds/{id}) names that deployment in deployment.

  • a381dde: Staff can delete the evidence a member submitted for a decided attempt: management.attempts.redactEvidence({ attempt, reason }) and domino attempts redact-evidence ATTEMPT --reason TEXT (review permission, test and live). The photo, text, URL or quiz answers, and what the quest's capabilities read from them, are deleted; the attempt keeps its outcome, completion and rewards. Attempts that submitted the same photo are redacted together. management.attempts.get answers the new redaction ({ at, actor, reason }). Undecided attempts are refused with attempt-not-decided, and attempts.retry refuses a redacted attempt with evidence-redacted.

  • 4443124: Quest code can read whether the member linked Telegram: ctx.telegram.connection() resolves to { userId }, the numeric Telegram user ID, or null. It needs no connected chat and doesn't ask Telegram; an account an app login vouched for or a member import brought in counts. It reads the account the attempt pinned, like member(), and is captured for replay. The testing kit answers it from facts.telegram.connection, and TelegramConnection is exported from @domino-sdk/relay/authoring.

  • b9270ff: Telegram Login, the member's Telegram account and its unlink now go through the generic sign-in surfaces, and Telegram's own members for them are deprecated. They keep working, with their routes, through SDK 1.x and are removed in 2.0.0:

    • relay.auth.telegram.start(mode) → relay.auth.start({ provider: "telegram", mode }).
    • relay.auth.telegram.account() → the "telegram" entry of relay.auth.signInAccounts().
    • relay.auth.telegram.disconnect() → relay.auth.disconnect("telegram").
    • useTelegramAccount() → useAccount("telegram"). Inside a Mini App, link with relay.auth.telegram.miniApp.connect(initData), then call invalidate([relayKeys.accounts, relayKeys.experience]) from useInvalidate().
    • useSignIn().withTelegram() → useSignIn().withProvider("telegram"); withProvider now takes any SignInProvider.
    • SignInOptions.telegram and useSignInOptions().telegram, TelegramSignInOption and SignInActions.telegram → signInProviders, below.
    • relay.auth.providers().telegram.login and management.participantSignIn().telegram.login → the "telegram" entry of signInProviders. TelegramSignInMethods and the new ParticipantSignInTelegram are written-out types, so editors show these two deprecations.
    • management.members.telegramAccount(member) and unlinkTelegramAccount(member, input) → members.accounts(member) and members.unlinkAccount(member, { provider: "telegram", subject, expectedRevision, reason }).

    useSignInOptions() and signInOptions(relay) add signInProviders, the open sibling of the oauth entries of options: every sign-in provider Domino configured, Telegram included, in Relay's order, each a ProviderSignInOption ({ kind: "provider", id, label, icon, start() }). A newer Relay may list a provider this SDK does not know; its label is its name. Against a Relay without signInProviders, the list holds its providers and Telegram Login. SignInMethods adds signInProviders and SignInActions an optional provider(provider) action. The starters render their provider buttons from signInProviders.

    Mini App sign-in (relay.auth.telegram.miniApp, useSignIn().withTelegramMiniApp, providers().telegram.miniApp, participantSignIn().telegram.miniAppBotId) is not deprecated.

  • 90a20c6: Telegram is now a plugin: management.connections.list() and domino connections get telegram include it, ready while the environment has a connected chat, attention with the chat's issue while Domino's bot can't read one, and disconnected without chats. Chats still connect only through the bot (management.telegram.connect(), unchanged), so connections.connect, update and disconnect answer 409 plugin-managed for Telegram and for any plugin that connects through its own flow. knownPluginSlots adds announcement, the slot Telegram posts quest announcements through. management.plugins.list() items gain console: { component } when Console shows the plugin's own component instead of the generated card, otherwise null; the schema defaults it to null, since older servers don't send it.

  • 8e8e24a: Telegram Mini App sign-in has its own entrypoint, @domino-sdk/relay/telegram (experimental): withTelegram(relay) adds relay.telegram to a client and answers the same client, relay.telegram.miniApp.signIn(initData) and .connect(initData) do what relay.auth.telegram.miniApp did, and telegramMiniAppInitData() reads the launch data. Apps that don't import it don't carry it. relay.auth.telegram.miniApp and the telegramMiniAppInitData exports of @domino-sdk/relay/browser and @domino-sdk/relay/react are deprecated, keep working through 1.x and are removed in 2.0.0.

    Telegram Login and Mini App sign-in now run in the Telegram plugin, with no change to what they accept or answer: members linked before keep signing in as the same member, and /v1/auth/telegram/mini-app takes the same requests. management.plugins.list() and domino connections get telegram list one more slot for Telegram, which knownPluginSlots adds: identity (members sign in with it).

  • 52ed037: Telegram's trackers and ctx.telegram now come from the Telegram plugin, with no change to what they record or read: telegram.messages() and telegram.reactions() trackers, the bot's webhook at /v1/integrations/telegram/updates, and ctx.telegram.connection() and member() behave as before, and published quests keep working. management.plugins.list() and domino connections get telegram list two more slots for Telegram, which knownPluginSlots adds: tracker-source (the plugin writes its trackers' records) and evaluator-capability (quest code reads it as ctx.<plugin>). A quest may now declare the telegram integration module, { provider: "telegram" }, and publishes; before, publishing refused it with unknown-integration-module. Declaring it is optional, since every quest gets ctx.telegram.

  • 2fe2d72: management.testMembers.history() and domino test-members history name each attempt's and skipped tracker outcome's quest by its authoring key as questKey, next to the quest ID in quest. It is absent for a quest created without a key. A script can filter a test member's history by quest key without calling quests get first.

  • 7fa2a06: Test members can submit photo quests. management.testMembers.uploadEvidence(member, file, { actionId }) uploads a JPEG or PNG of at most 5 MB as the test member's photo, and act submits its id as evidence. domino test-members act MEMBER QUEST --photo FILE does both, so the quest's real image check and the review queue can be proven from the CLI. The campaign-testing reference now points photo evaluator tests at facts.images.review.

  • 06046f1: X trackers can pay the member whose post a collected post quotes or reposts. automatic({ tracker, beneficiary: "quoted" }) pays the quoted post's author and beneficiary: "reposted" the reposted post's author, so a "get noticed" quest can reward members an account picks up: x.search({ query: "from:examplelabs filter:nativeretweets" }) with beneficiary: "reposted". xAccountRoles gains both. A post that is no quote or repost is skipped with no-account ("Post is not a quote" / "Post is not a repost"), and a quote or repost of the author's own post with beneficiary-is-author. Like inReplyTo, these quests don't reward posting and don't raise x-post-rewards-ungated. xAccountRoles and SourceRoles list the roles this SDK knows, and a later release may add roles to them, so code that switches over them needs a default branch.

Patch Changes

  • ee3f488: Bulk management writes (catalog.deploy, catalog previews and drafts, deployments.preview, releases.publishBatch, tracker record ingest and memberImports.addRows) refuse a body with more than 2,000,000 JSON values with 413 payload-too-large before parsing it, whatever its byte size; other management writes refuse one past 500,000. A server handles one body over 4 MiB or 500,000 values at a time and answers another 503 with Retry-After when it has waited 10 seconds. memberImports.addRows now takes at most 64,000,000 bytes, like the other bulk writes, so a single batch above 64 MB from an older CLI gets 413. domino import now splits rows into batches of at most 500 rows, 16 MB and 1,000,000 JSON values, sends a row larger than that in a batch of its own, and refuses a row too large for one call (63 MB or 1,990,000 values) before uploading anything, naming its line.

    The management client, the CLI and the participant client now send any call again after a 503 with Retry-After, within its deadline and attempts, including writes without an action ID: Relay answers such a 503 only to a request it refused before doing anything. Before, those writes were sent once.

  • f13cfb6: A domino login CLI session creates and scopes personal API keys in test only. management.tokens.create with environment: "live", and management.tokens.scope on a live key, now answer 403 console-approval-required from a CLI session, whatever X-Relay-Environment selects: a live key would carry the live operations (points, attempt confirmations, reviews, pauses) that the CLI session itself is refused, and would outlive domino logout. Create and change live API keys in Console (Settings → API keys). A CLI session still lists its keys and revokes any of them, live included, and Console sessions are unchanged.

    This changes a 1.x behaviour, an exception Peter Szoke granted on 2026-10-15: no request or response shape changes, and the refusal is a typed 403 that says where to go. domino tokens create --environment live prints that refusal, and domino tokens --help says live keys come from Console.

  • eed1808: Participant cookies on HTTPS now use __Host- names (__Host-relay_session and the sign-in binding cookies), so one app on domino.page can no longer set them for another, and a cookie name the browser sends twice reads as absent. createParticipantBackend forwards the OAuth return binding as __Host-relay_auth_return when the browser has it, otherwise relay_auth_return, and forwards neither when a name appears twice.

    Members signed in when this deploys are signed out once and sign in again, and a sign-in started just before the deploy fails once and starts over. Older SDK versions keep working: through an identity integration, Relay still sets and reads relay_auth_return.

  • 94ca2b5: Unlinking an imported or vouched account now releases it. management.members.identityStates.unlink (and domino members unlink-identity and the deprecated per-kind unlink methods) on an X, Discord or Telegram account or wallet that a member import or an app login vouched for, and that the member never signed in with or proved, removes it from the member instead of keeping it bound: its owner can then link it and sign in with it. The answer shows it unlinked with canRelink: false, and later imports and app-login vouching refuse it for that member with identity-unlinked. Accounts the member signed in with or proved keep the previous behaviour: the link is cleared, ownership stays, and it can be relinked.

  • d4fcade: Role delivery helpers ignore announcement rows. useExperience().deliveries, roleDeliveryStatus and the watch behind them count only grant-role deliveries, so a quest that only posts an announcement in a Telegram chat or Discord channel no longer shows "Role delivery pending" or "Role granted", and its pending post is no longer polled every 5 seconds. A quest with a Discord role reward reports its role as before, also when it announces. roleDeliveryStatus applies the same rule to raw me.rewardDeliveries() rows, and RoleDelivery gains an optional action. me.rewardDeliveries() itself still returns every delivery.

  • 8064983: Deployment previews, records and history pages (deploymentPreviewSchema, deploymentRecordSchema, deploymentHistoryPageSchema, and the management.deployments methods that return them), collectionSlotSchema and questCollectionSchema read collections without rejecting unknown fields, so a field a newer Relay adds to a deployment collection no longer fails the response. Collection inputs (collectionSlotDeclarationSchema, catalogDeploymentSchema and saved draft changes) still reject unknown keys.

  • 980e6ca: Sign-in rate limits count an IPv6 client by its /64, so every address in one /64 shares the per-IP limits on email, wallet, OAuth and Telegram sign-in starts, domino login device starts and staging password attempts. An IPv4-mapped IPv6 address counts as its IPv4 address. Per-address limits now count per client: three sign-in codes per email address and ten wallet challenges per wallet address from one IP address every ten minutes, so another client hitting them no longer refuses that address's own sign-in, on this project or another. Each project and environment also caps an address at ten sign-in codes and thirty wallet challenges every ten minutes from all clients together.

  • d683d00: Sign-in rate limits per IP address count each member of a Domino-hosted app on their own. Hosted apps reach the API from Cloudflare's shared Worker address, so until now every member of every hosted app shared one set of per-IP limits. Domino now attests the visitor's IP on every request a hosted app's server sends to the API, with no app change or rebuild. A request from Cloudflare's shared Worker address without Domino's attestation, such as from an app you run on your own Cloudflare Workers, is counted per project and environment instead of with every other Worker.

  • 081806c: Personal keys (ak_…) and MCP OAuth tokens that Clerk does not recognize now count against the client that presented them, on the management API, git and the MCP server: after 30 in 10 minutes from one IPv4 address or IPv6 /64, counted by each API server on its own, every ak_ and MCP token from it answers 429 rate-limited with Retry-After until the window ends. Expired, revoked and other refused credentials Clerk knows are not counted, and neither is Clerk refusing Domino's own secret key. A refused value presented again within a minute can be refused without asking Clerk, and a value no Clerk credential can be (under 16 characters after its prefix, over 4096, or with a character a bearer token cannot hold) is refused with 401 invalid-credential without asking Clerk. When Domino is checking many new credentials at once, a key or token it has not accepted recently can answer 503 with Retry-After: 1. Clerk's own rate limit now answers 503 with Clerk's Retry-After.

  • 114e0c7: Telegram and Discord install links now tell whoever opens them which Domino project they connect to. The Telegram bot's reply to a link, before the chat picker, names the organization, the project's name and slug, and the environment, and so does its Connected … reply. A Discord install link sent to a server's administrator (Send to your server admin) first opens a page naming the same, with a Continue to Discord link; the page changes nothing, so a link preview that fetches it stays harmless, and the page shown after connecting names the project too. Creating either link (management.telegram.connect(), POST /management/v1/integrations/discord/links) now reads the organization's name from Clerk, and answers 503 rather than issue a link that names nobody when Clerk can't answer. Links made before this change still work and name no project. Console's own Discord installation is unchanged.

  • 92ee725: Connecting a Discord server or Telegram chat where Domino's shared bot already holds rights now takes someone who holds those rights too. In Discord, installing the bot (Console's Connect Discord or a shared install link) refuses a user who is neither the server's owner nor an Administrator when the bot already has a permission there they lack, such as Manage Roles another organization gave it; the install page says "Domino's bot already has permissions in this server that you don't have. Ask a server administrator to connect it." and nothing is connected. A fresh install, where the bot holds nothing beyond @everyone, still needs only Manage Server. In Telegram, an administrator who lacks an administrator right the bot holds in the chat, such as posting messages, is refused with a reply naming the missing rights; the chat's creator always connects it. Reusing a Discord server the organization already connected is unchanged, and existing connections are not revisited.

  • 10826dc: An organization administrator's CLI session lists the project's identity integrations (app login keys) and their claimed kinds: domino identity-integrations list and domino identity-integrations kinds, or management.identityIntegrations.list() and kinds(). They return each key's origin, identity kind and secrets' expiry and last use, never a secret. Creating, rotating and revoking keys and their secrets stays in Console; non-admin CLI sessions and every key type are refused.

  • c391c86: Documentation only: the JSDoc for pointLedgers.create, pointLedgers.update, calendar.configure, profiles.configure and the campaigns methods now says what live refuses. In live, every definition write is refused for every credential, Console included, unless it applies an approved promotion; campaigns.enroll and campaigns.withdraw are operations that need enroll and work in live with a personal API key.

  • 1c61073: domino tokens list|create|scope|revoke manage your personal API keys from a domino login session, as management.tokens does. domino tokens create --name NAME --permission P [--ingest-tracker T] [--days N] issues a key for the selected project and environment and prints its one-time token and expiry; --json prints the SDK's answer, so domino tokens create … --json | jq -r .token | domino secrets set NAME stores it. A personal key expires after days, 1 to 90, default 30; --days 91 is refused before anything is sent. domino tokens list shows each key's expiry. API keys still cannot manage keys (credential-not-allowed), and a credential that must not expire is a service key, which an organization admin creates in Console.

  • aaf28f9: Quest submissions are limited to 120 an hour per member in each project environment, counted together across POST /v1/me/attempts, POST /v1/attempts, POST /v1/me/submissions/:id/submit and POST /v1/attempts/:id/retry. Past it they answer 429 rate-limited with Retry-After, before the request is read. The quest controllers keep the saved draft, so resume() continues it. The participant reference in the Domino skill states the limit.

  • 9f39ea4: An operator's reversal of a referral payout stays reversed. Reversing all or part of a referral payout (Console point history Reverse, or management.points.record({ kind: "reverse", entry })) writes those points off on the referral account, so no later reconcile pays them again; earlier, the inviter's next invitee activity or a new acceptance of their code paid the reversed amount back. Later bonus growth still pays, and restoring a revoked qualification does not repay the reversed points. earned in the referral summary and the management referral accounts excludes the reversed points, so a reversed payout no longer reads as earned.

  • b866593: Behavior change: completing a referral program's qualifying quest no longer closes the member's invitation. A friend who claimed an entry quest at sign-in, for example through management.passes.verify, can still accept an invitation afterwards; accepting qualifies them in each enabled program whose quest they already completed, on their earliest unrevoked completion, and pays the inviter and the friend once. Completing any other quest still makes referrals.accept and testMembers.acceptReferral refuse with invitation-closed, and a revoked qualifying completion neither closes the invitation nor qualifies. Points the friend earned before accepting don't earn the inviter a percentage bonus.

  • 1cdc64b: Staging a commit the project's hosted repository doesn't have (a mistyped SHA, or a commit not pushed yet) now answers a certain 422 commit-not-found before any build is reserved, instead of an uncertain 502 upstream-failed. domino stage reports it as REJECTED with apiCode: "commit-not-found", saves no operation to resume and prints no retry command: push the commit, then stage again. Other GitHub failures during the commit check still answer 502 and stay uncertain.

  • 3d2f654: Staging's participant API is now private, like staging itself. A participant request that names a staging address (<slug>--staging.domino.page) in X-Relay-App-Origin answers only when it carries the staging pass, which the staging gateway gives browsers signed in to staging; anything else is refused with 403 staging-access-required. Apps on staging keep working with no change: the gateway adds the pass to the requests it hands the app and to legacy /relay/* calls, and proxyParticipant forwards it with the browser's other cookies. Only callers that sent a staging origin straight to the API, outside the staging address, see the new refusal. The retired staging-<slug>.domino.run origin now selects nothing (403 "Application origin is not registered or is offline"). Previews, live apps and identity integrations are unchanged.

1.61.0

Minor Changes

  • f0ce453: RelayProvider follows the EVM wallet a participant signed in with through useSignIn().withWallet() or a wallet option of useSignInOptions(), so the starters need no code for it. When the wallet switches to another account (accountsChanged, compared case-insensitively) or reports none because it was locked or disconnected, the provider signs out as useSignOut() does and the app shows its sign-in again; it does not sign the new account in. The same account, including the event wallets send on first connect, changes nothing. After a reload the provider finds the wallet again by name and checks eth_accounts without a prompt. WalletSigner gains an optional watchAccounts(listener), which evmSigner() implements, and Eip1193Provider an optional on and removeListener.

  • b369a75: A project connects a Discord server that another project of its organization already connects, ready, in the same environment, without installing Domino's bot again. Any credential with publish permission can: an automation key, a CLI session, a personal API key or Console (Integrations, Or reuse). Installing the bot into a new server stays with an organization administrator in Console.

    • management.discord.servers() and domino integrations discord servers list the servers the project can connect.
    • management.discord.connect(server) and domino integrations discord connect --server <id> connect one, once the bot still sees it. They refuse with 409 server-not-connected-in-organization for any other server, 409 integration-connected when the project already has a Discord connection, and 422 server-not-reachable when the bot lost the server. Connecting the server the project already connects changes nothing.
    • management.discord.connection() and domino integrations discord status read the project's Discord connection.

    The project's audit records each connection as discord-server-connected, with who connected it and the project whose connection it reused. An automation key and a CLI session connect only in test.

Patch Changes

  • e71f881: domino check runs each badge, point ledger, offer, leaderboard, referral, dashboard and tracker entry in a separate Node process and parses its export with the schema Relay parses it with at deployment. A definition that fails, such as an offer without stock, reports a DEFINITION_INVALID error diagnostic with its file, export, field path and message, and fails the check instead of failing later in staging. defineOffer, defineBadge, defineLedger, defineLeaderboard, defineReferral, defineDashboard and defineTracker still throw a ZodError for invalid input, and its message now names the kind, key and fields, for example Offer "ladder" is invalid: stock: Invalid input: expected number, received undefined. Staging shows that message when its data setup fails. A deployment, preview or staging activation with an inline or described definition that fails its schema now answers 422 validation-failed with that message, where it answered 400 Invalid request.

1.60.0

Minor Changes

  • d258795: A budget can cap what one person gives, not only what one member receives. budget({ key, scope: "author", period, limit, cost }) counts per tracker record author: the account that caused the record, such as a reaction's reactor, whether or not a member linked it. Put it in a tracker quest's budgets beside a "member" budget to cap both sides, for example a tip paid to a message's author with beneficiary: "messageAuthor": at most 20 tips a day per receiver and 10 a day per reactor. A tip over either cap is rejected with budget-exhausted, permanently; other reactors' tips keep paying. It works on telegram.reactions(), discord.reactions(), kudos recipients and X inReplyTo replies. Author budgets share keys, calendar periods and the atomic commit with the other budgets, and imported completions don't count toward them.

    An author budget needs a tracker trigger. domino check reports AUTHOR_BUDGET_TRACKER_REQUIRED on another quest, domino build and domino deploy refuse it, and Relay refuses its publication.

    The types SDK 1.x code reads are unchanged: Budget keeps scope: "member" | "project", and a quest's description lists author budgets in the new optional authorBudgets (AuthorBudget, authorBudgetSchema from @domino-sdk/relay/schema), apart from budgets. budget() returns an AuthorBudget for scope: "author". A member's quest budgets (QuestBudgetView) leave author budgets out, since they cap someone else's giving.

  • d41f5da: relay.me.get() and useMe() now return the member's clock: { now, offsetMs }, the clock Relay judges their activity on. A test member's clock runs ahead after the staging toolbar's Next day or domino test-members advance; every other member's offsetMs is 0. New memberTime(me, at?) from @domino-sdk/relay/browser turns a real instant into the member's time, so an app that shows campaign phases or open quests by date follows an advanced test member. clock is optional, and memberTime returns real time when a Relay older than it omits it. On staging, the toolbar's domino:test-state-changed event now carries detail: { change, actingAs }: what changed and which test member the page is signed in as.

  • e899f47: Member imports carry referrals. A row's optional referral names the member's inviter (another row's ref, or the sign-in identity of an existing member), when they accepted, and the referral programs they already qualified in (qualified: [{ program, at }]). Applying records the referral and the qualifications without paying any referral reward, milestone or bonus; they count toward the inviter's qualified friends, and the friend's later points pay the program's bonus. An inviter's row applies before the rows that name it. New refusal codes: unknown-inviter, self-referral, inviter-already-accepted, referral-cycle, inviter-refused and unknown-referral-program. The check summary adds referrals (accepted, and qualified by program), management.referrals.participants() names the import that recorded a relationship or qualification in import, and management.members.get() returns the member's accepted referral in an optional referral (inviter, acceptedAt, suspended, import).

  • 8ee2000: A member import vouches for Telegram and Discord accounts too. A row's accounts takes { kind: "telegram", subject } and { kind: "discord", subject }, each a numeric user ID, at most one of each per member, from domino import and management.memberImports.addRows. They work like a vouched X account: Telegram and Discord tracker records, quest checks and Discord role checks trust them from when the import attached them, nobody else can link them, and the member's own Telegram or Discord link replaces them. They never sign in: a Telegram or Discord sign-in with one is refused with sign-in-method-disabled. A row naming a second account of a kind, or another one than the member's, is refused with the new MemberImportRefusalCode.TelegramAccountSelected (telegram-account-selected) or DiscordAccountSelected (discord-account-selected).

    @domino-sdk/relay/server adds memberImportVouchedAccountSchema, memberImportFullRowSchema and memberImportFullRowsInputSchema, with the types MemberImportVouchedAccount, MemberImportFullRow, MemberImportFullRowInput and MemberImportFullRowsInput, for rows with every kind of vouched account; addRows takes MemberImportFullRowInput[]. memberImportAccountSchema, memberImportRowSchema and memberImportRowsInputSchema stay the X-and-wallet form. Member identity states carry imported: true for an X, Discord or Telegram account an import attached, and so does a connected ManagedTelegramAccount.

    An imported X, Telegram or Discord account now pays tracker records from the moment it was imported. Before, a vouched X account had no link time, so its records were skipped as historical. Members imported before this release keep that unknown link time until they link their X account themselves.

  • f0d5b8e: Offers can be archived and restored. management.offers.archive and unarchive (POST /management/v1/offers/OFFER/archive|unarchive), domino offers archive|unarchive OFFER --reason, Console Settings → Resources and the offer's card in Console Rewards archive one in test, and a promotion carries it to live. Each returns { resource, changed }, typed by the new offerLifecycleResultSchema, with resource.kind offer. An archived offer leaves member offer reads and refuses redemption with offer-paused; management reads keep it, with the new archivedAt field on Offer, and its redemptions can still be canceled. management.resources.list() and domino resources list list offers in a new offers array (managedResourceListSchema), beside the existing kinds. A rollback now archives an offer the earlier promotion did not carry.

  • 6b5228b: Quests can announce a completion in a connected Telegram chat or Discord channel. List telegram.postMessage({ chat, text, replyToTriggeringMessage }) or discord.postMessage({ channel, text, replyToTriggeringMessage }) in the new optional announcements of defineQuest, beside rewards (at most three; an array or a function of the settings). The text is a template with the placeholders {beneficiary}, {author}, {points}, {rewards} and {quest} (announcementPlaceholders); people are mentioned through their linked account on that platform. Relay posts after the rewards commit, as a reward delivery with the new RewardDeliveryAction.PostMessage action and an empty roleId, retried with backoff and paced per chat; a failed post never undoes the rewards, and management.retryRewardDelivery(id) retries one. Descriptions carry them in the new optional announcements field (announcementSchema). discord.channels({ postable: true }) lists channels the bot can't post in as unavailable, and TelegramChat gains an optional canPost. The CLI's authoring reference documents announcements.

  • ea1f033: Revert an applied or stopped member import: management.memberImports.revert(id, { reason }) starts it, and domino imports revert IMPORT --reason TEXT starts it and waits. In the background, one row at a time, it reverses the applied rows' opening balances (taking only points members still have; unreversedPoints lists the rest), revokes their imported completions (reason import-reverted, freeing the occurrence) and removes the wallets, accounts and identities they linked unless the member signed in with one since (keptLinks). Members stay in the project. A reverting or reverted import reads as applied with revert set, and is reverted once; the same actionId answers the import again. Rows applied before imports recorded their links keep them (linksUntracked). Reverting needs publish permission and works in live from API keys and CLI sessions too. Console shows Revert import on applied and stopped imports. MemberImport gains revert: the reason, who asked and when, rows reverted so far, then revertedAt and the outcome.

  • 9a7ad11: Stop a member import while it applies: management.memberImports.stop(id) and domino imports stop IMPORT let the row being applied finish, apply no more rows, and end the import: it reads as applied with appliedAt null and stoppedAt and stoppedBy set, and its remaining rows stay ready. A stopped import can't be applied again; import the remaining rows in a new import. Stopping needs publish permission and works in live from API keys and CLI sessions too. Console shows Stop import on an applying import. MemberImport gains stoppedAt and stoppedBy; check stoppedAt to tell a stopped import from a fully applied one.

  • df3a08c: A test member's clock moves by hours and minutes, or to an instant, so time-of-day rules can be tested: block edges, or midnight in the project's time zone. management.testMembers.advance(member, input) takes { actionId } with one of days (1–30, unchanged: project-calendar days that keep the time of day), hours (1–720), minutes (1–43200) or to, an ISO 8601 instant with an offset that must be later than the member's clock (else 422 test-member-clock-past). From the CLI, domino test-members advance MEMBER takes one of --days, --hours, --minutes or --to. The 90-day limit holds across units (422 test-member-clock-limit): days still add up to at most 90 and land no later than the 90th date, and hours, minutes and to never move the clock past 90 days from now. offsetDays still counts the days advanced with days; hours, minutes and to leave it unchanged, so read the member's date from today. @domino-sdk/relay/server exports testMemberClockAdvanceSchema and TestMemberClockAdvanceInput for every form; testMemberAdvanceSchema and TestMemberAdvanceInput stay the days form.

1.59.0

Minor Changes

  • 1d5a195: management.attempts.acceptAnyway({ attempt, reason }) (POST /attempts/<id>/accept-anyway, review permission) accepts a rejected attempt anyway, for any member, reversing a reviewer's or an evaluator's rejection. The acceptance pays as a reviewer's approval would, with the normal completion.created webhook; the rejection stays in the attempt's events beside a staff-decision with decision: "accept-anyway" and the reason, and the retry cooldown ends. Only the member's latest attempt on a quest that isn't archived qualifies (409 attempt-superseded, quest-archived), and a cap the acceptance would break refuses it instead of rejecting it again (409 completion-cooldown, budget-exhausted, completion-revoked, already-completed). management.attempts.get answers supersededBy, the member's newer attempt on the quest, or null. The CLI adds domino attempts accept-anyway ATTEMPT --reason TEXT.

  • a0c77e4: Archiving or unarchiving a group, pausing or resuming a tracker, and changing the project's schedule state take an optional reason, which the audit log records: management.groups.archive(group, { reason }) and unarchive(group, { reason }), management.trackers.pause(reference, { actionId, reason }) and resume, and management.schedules.setState(state, { reason }). A reason is a trimmed string of 1–500 characters, like the other lifecycle reasons; leaving it out works as before. The CLI adds --reason TEXT to domino groups archive|unarchive, domino trackers pause|resume and domino schedules pause|archive|resume. Console asks for a reason in a dialog for each of these, and the project schedule menu no longer applies a state as soon as you pick it.

  • 424863f: Operators can restore an entitlement they voided. management.entitlements.restore(entitlement, { revision, reason }) (POST /management/v1/entitlements/<id>/restore, review permission) and domino entitlements restore <id> --revision <n> --reason <text> make a void entitlement available again at the tier an accepted completion still earns, and bring back any referral review the void settled. Restoring is refused with 409 entitlement-status when a completion revocation voided the entitlement or no accepted completion still earns it (the body names the completion to restore instead), when canceling an offer redemption voided it, or when a referral reward was voided before voids kept their reviews. Console Rewards and member detail offer Restore reward on a voided reward, and Audit history lists each restore with its reason.

  • 790aa5d: Operators can restore a completion they revoked by mistake. management.completions.restore({ completion, reason }) (POST /management/v1/completions/<id>/restore, review permission) and domino completions restore <completion> --reason <text> re-apply what the revocation recorded, not a fresh reward evaluation: the points it reversed are awarded again (points it could not reverse are not), its badges, group memberships, tiered rewards, Discord role grants, referral qualifications and streak milestone come back, and budgets stay spent. The result (CompletionRestoration) lists what came back. Restoring is refused with 409 completion-not-revoked when the completion isn't revoked, and with 409 occurrence-taken (naming the completion that holds it) when the revocation freed the occurrence and the member completed it again. members.get(id).revokedCompletions lists up to 25 revoked completions, each with its revoke's reason and result. Console member detail and attempt detail offer Restore completion, and the attempt history and Audit history show the restore beside the revoke.

  • 850a252: settlementPayoutCsv(exported) turns the result of management.leaderboards.settlementExport(id) into the payout list as CSV, with the columns member,score,rank,share,amount,unit,wallets. domino leaderboard-settlements export now prints it through this helper, unchanged, and Console's prize settlement panel offers Download payout list (CSV) for any pool that is not points, with the same content.

  • 7ac13ef: Solana transactions from ctx.wallet.transactions() now carry signer: whether the linked wallet signed the transaction. It is false for a transfer someone else sent into the wallet, and for results captured before it existed. Check it before rewarding an action. The new solana-action snippet completes a quest once per transaction a linked wallet signed that called your program, optionally receiving at least an amount of a token.

  • 5b94440: Anchor instruction decoding for ctx.wallet.transactions(). Saving a settings.solanaProgram() setting now stores the program's Anchor IDL (legacy or 0.30+), read from its on-chain IDL account, in the published release's new optional solanaIdls (SolanaProgramIdl: { program, source, idl }, by setting key). For a program without an on-chain IDL, an operator pastes one in Console or sends the new optional solanaIdls: { [settingKey]: idl | null } with quests.updateSettings() (anchorIdlSchema, AnchorIdl). With an IDL, each transaction gains optional instructions: the program's top-level and inner instructions in call order, decoded as { name, args, accounts } (WalletInstruction). An instruction the IDL does not describe is skipped. The new instruction(tx, name) helper in @domino-sdk/relay/authoring returns the first matching instruction's args, or null. The testing kit's facts.wallet.transactions accepts instructions.

  • 95cb8d8: On-chain action checks on Solana. ctx.wallet.transactions({ program, since, afterActivation?, mint?, address? }) reads the successful, finalized transactions of the member's linked Solana wallets since since that invoked program in a top-level or inner instruction, oldest first. It returns { program, chain, mint, since, at, complete, transactions: [{ wallet, signature, slot, timestamp, programs, balanceChanges: [{ mint, delta }] }], observedAt } (WalletTransactions, WalletTransaction, WalletBalanceChange, walletTransactionsSchema), with mint "SOL" for lamports (rent included, the fee the wallet paid left out) and delta a signed integer string, or null when the member has no linked Solana wallet, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. With mint, only each wallet's associated token account for that mint is read. since may be at most 30 days before the evaluation (maxWalletTransactionLookback); each wallet's read fetches at most 1,000 transactions (maxWalletTransactions), and a wallet with more returns its newest ones with complete: false instead of throwing. WalletReader.transactions is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The program comes from the new settings.solanaProgram({ label, default }) setting, a base58 address (SolanaProgram, isSolanaProgram) stored as a text setting with picker: "solana-program". Publication, Console settings edits and unarchiving check that the account exists on Solana mainnet and is executable.

    New pure helpers in @domino-sdk/relay/authoring read one transaction: received(tx, mint) and spent(tx, mint) return the wallet's gain or loss as an integer string, or "0"; swapped(tx, { into?, from? }) checks that one mint went down and a different one went up; invoked(tx, program). The testing kit answers ctx.wallet.transactions() from facts.wallet.transactions.

Patch Changes

  • aaa07ad: The SDK and CLI depend on Zod 4.6.5, whose schemas keep their methods on a shared prototype instead of binding about 7 KB of them to every schema instance. Importing the SDK takes far less memory: a Relay backend process that loads the server entry starts with about half the heap. Quest settings.datetime() values, member-import times and X post createdAt values (including hand-written testing fixtures) still accept minute-precision instants such as 2026-11-01T00:00Z, which Zod 4.6 alone rejects. An X post read without a media field still gives an empty list.

    One tracker data schema change after a rebuild: a field declared as z.number().min(0).int() now stores minimum: 0, so Relay refuses negative values at ingestion that the app's own schema already refused. Zod 4.3 stored the safe-integer minimum for that order of checks.

1.58.0

Not published to npm. Its changes first reached npm in 1.59.0.

Minor Changes

  • 321763b: management.discordRoles.list() lists every Discord role the project's reward deliveries have granted in the environment, with the quests that requested it and its deliveries counted by status (pending, delivered, failed). It reads the whole delivery history, so a role granted only before the newest page of management.rewardDeliveries still appears. The response schema is discordRoleRewardsSchema.

Patch Changes

  • a2318dd: stagingPathSchema refuses paths with control characters and accepts only a path that resolves on the staging host. Browsers drop tab and newline from a URL, so a path such as /<tab>/example.com used to pass and open another site after a staging sign-in.

1.57.0

Not published to npm. Its changes first reached npm in 1.59.0.

Minor Changes

  • 7012d1c: Contract state and Cosmos staking reads in quest evaluators.

    ctx.wallet.call({ call, args?, address? }) calls one view or pure EVM contract function once per linked EVM wallet, every call at one block, through dRPC on the chains management.walletBalanceChains() lists: state a contract keeps per address, such as stakedOf(address), getVotes(address) or userInfo(uint256, address). { wallet: true } in args stands for each wallet's address. It returns { call, chain, contract, returns, total, wallets: [{ address, value }], at, observedAt } (WalletCall, walletCallSchema), with integers as decimal strings and total their sum (null for bool and address returns), or null when the member has no linked EVM wallet. A revert is an author-error carrying the contract's reason. The call comes from the new settings.contractCall({ label, default }) setting (picker: "contract-call"): the contract's CAIP-10 ID and a human-readable signature, such as eip155:8453:0x… function stakedOf(address) view returns (uint256) (ContractCall, isContractCall, parseContractCall, parseContractFunction). Only view or pure functions with elementary parameters that return one uint, int, bool or address are accepted; publication, Console settings edits and unarchiving also check the chain.

    ctx.wallet.delegations({ chain, validator?, address? }) reads the native staking delegations of the member's wallets linked on a Cosmos SDK chain Relay lists, at one block, in base units of the chain's staking denomination: { chain, denom, validator, total, wallets, validators, at, observedAt } (WalletDelegations, walletDelegationsSchema), optionally only to one validator, or null when the member has no wallet on that chain.

    Both follow ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. WalletReader.call and WalletReader.delegations are optional in the type so that existing test doubles compile; Relay supplies them to every evaluation, and the testing kit answers them from facts.wallet.call and facts.wallet.delegations. New contract-state and cosmos-staker snippets accept members whose view call reaches a minimum, or who delegate at least a minimum, optionally to a configured validator.

  • 59a01d8: On-chain action checks on EVM chains. ctx.wallet.events({ event, account, since, afterActivation?, where?, address? }) reads the events of one contract whose indexed address argument account is one of the member's linked EVM wallets, from since to the chain's finalized block (its safe block on a chain without finality), through dRPC eth_getLogs on the chains management.walletBalanceChains() lists. It returns { event, chain, contract, since, at, finality, logs: [{ wallet, block, timestamp, tx, logIndex, args }], observedAt } (WalletEvents, walletEventsSchema) with decoded arguments, or null when the member has no linked EVM wallet, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. where holds equality or { gte } conditions on other arguments (WalletEventCondition); afterActivation skips events before the quest's activation. since may be at most 30 days before the evaluation (maxWalletEventLookback), and a read over more than 3,000,000 blocks or 1,000 logs (maxWalletEventLogs) throws author-error. WalletReader.events is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The event comes from the new settings.contractEvent({ label, default }) setting: a CAIP-10 contract, a space and the event's Solidity signature with argument names and indexed markers, such as eip155:8453:0x… Transfer(address indexed from, address indexed to, uint256 value) (ContractEvent, isContractEvent, parseContractEvent, ContractEventParts), stored as a text setting with picker: "contract-event". An event without an indexed address argument is refused. Publication, Console settings edits and unarchiving also check that the chain reports a finalized or safe block and that the address holds a contract.

    The testing kit answers ctx.wallet.events() from facts.wallet.events. A new onchain-action snippet rewards each new matching event since the later of the quest's activation and the wallet's link, with an optional minimum amount, keyed on chain:tx:logIndex.

  • 956b721: NFT holding checks on EVM chains and Solana. ctx.wallet.nfts({ collection, address? }) counts the NFTs of one collection the member's linked wallets of its family hold: on EVM, an ERC-721 contract's balanceOf(owner), or one ERC-1155 token ID's balanceOf(owner, id), through dRPC on the chains management.walletBalanceChains() lists; on Solana mainnet, the NFTs whose Metaplex metadata names the collection with verified: true (SPL Token and Token-2022 accounts holding exactly 1 of a 0-decimal, supply-1 mint; compressed NFTs and Metaplex Core assets are not counted). It returns { collection, chain, standard, total, wallets: [{ address, count }], at, observedAt } (WalletNfts, walletNftsSchema; standard is "erc721", "erc1155" or "metaplex"), or null when the member has no linked wallet of that family, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. WalletReader.nfts is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The collection comes from the new settings.nft({ label, default }) setting, a CAIP-19 asset type such as eip155:1/erc721:0x…, eip155:8453/erc1155:0x…/1 or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:<collection mint> (NftCollection, isNftCollection, parseNftCollection, NftCollectionParts), stored as a text setting with picker: "nft". Publication and Console settings edits check it: an EVM chain and that the contract reports the standard through ERC-165 supportsInterface, so a token contract or the wrong standard is refused when it is set; a Solana mint must be a Metaplex collection NFT, so a fungible mint, a mint without Metaplex metadata, a print edition or a member NFT is refused. Unarchiving checks it too, and a platform without its dRPC key refuses every EVM collection. ctx.wallet.balance() pointed at an ERC-721 or ERC-1155 contract now throws author-error naming ctx.wallet.nfts() instead of reading a token count; it asks ERC-165 alongside decimals() on every token read.

    The testing kit answers ctx.wallet.nfts() from facts.wallet.nfts. A new nft-holder snippet accepts members whose linked wallets hold at least one NFT from the configured collection, on EVM or Solana.

  • 1770ada: A seventh starter, loyalty: members join a campaign, earn points from its steps, join a members' group through a quest's group membership reward, and spend points on offers, one of them limited to the group with inGroup. Console offers it and domino create --template loyalty creates it.

    Starters are now an open list, so adding one no longer changes the SDK's types. starters lists every starter as a Starter with a plain string id, offeredStarterSchema checks an ID against it, and projectCreateInputSchema and repositoryCreateSchema accept any offered starter; management.createProject uses the first. starterTemplates, StarterTemplate, StarterId, starterIdSchema, projectInputSchema and createRepositorySchema are deprecated: they keep the first six starters and are removed in 2.0.0. To upgrade, replace starterTemplates with starters and starterIdSchema with offeredStarterSchema, and type a starter ID as string.

  • cec850f: Create and change a tracker of every managed source without a deployment, not only X search. management.trackers.create and management.trackers.update (POST /trackers, POST /trackers/:tracker/update) take a source: { kind: "x-search", query }, { kind: "discord-messages", text? }, { kind: "discord-reactions" }, { kind: "telegram-messages" }, { kind: "telegram-reactions" }, { kind: "youtube-shorts" } or { kind: "tiktok-videos" }. Each is stored as the source its builder makes, such as telegram.reactions(), so quests read a data tracker as they read the same tracker in code. query alone still creates an X search tracker; create takes exactly one of query or source. An update's source replaces the source's settings but never its kind, which is refused with 422 validation-failed. Turning Discord message text off erases the text already stored. managedTrackerCreateSchema validates the new input, while trackerCreateSchema keeps its X-only shape. managedTrackerSource(source) reads a published source by these names, and managedTrackerSourceKinds lists them.

    domino trackers create KEY --source <kind> creates one from the CLI, with --text on for Discord messages; domino trackers update TRACKER --text on|off turns message text on or off. Console Trackers → New tracker offers every source.

  • 94726d8: Organization automation keys (dak_): a credential for CI and headless coding agents that no person owns. An organization administrator creates one in Console Settings → API keys for every project or a chosen list, with chosen permissions and an optional permission to create projects. It works as RELAY_MANAGEMENT_TOKEN, with domino login --token-stdin and the Git credential helper, in the test environment only: live writes, administration and minting other credentials are refused.

    The management client adds automationKeys (list, create, addSecret, revokeSecret, revoke) with automationKeyInputSchema, automationKeySchema, issuedAutomationKeySchema and automationKeyPermissionSchema. accessSchema gains an optional credential that /access and domino whoami --json fill for an automation key with its kind and scope.

  • 6d9f0de: telegram.messages() is a tracker source of the messages members post in the project's connected Telegram groups and supergroups, one record per message with the poster's Telegram account as author. data is TelegramMessageData: chatId, chatType, messageId, replyToMessageId and threadId, never the text. Bots', service and channel messages are not recorded. In SDK 1.x it is a custom source on the reserved namespace telegram-messages, which Relay alone writes. The new telegram-contribution snippet rewards posting once per member per day.

  • 8d11f64: wallet.balances({ chain, token }) is a tracker source of daily balance samples that Relay writes itself. Once per project-calendar day, at a time members can't predict, Relay reads one token's balance in every wallet linked by a member who joined the project, every wallet of a run at one pinned EVM block or Cosmos height (on Solana, every read must reach the run's pinned slot). chain and token are those of ctx.wallet.balance(). Each record is one wallet, keyed by its CAIP-10 account on the sampled chain, which is also its author; each sample is a new observation whose occurredAt is the sample time and whose data is WalletBalanceSample (amount in base units, decimals, block). A provider outage retries the same block later that day and never writes a zero, so a day with no successful sample breaks a streak({ days }). Reward holding for days with automatic({ tracker, where: { path: "amount", gte: { setting: "threshold" } } }) and streak({ days }). In SDK 1.x it is a custom source on the reserved namespace wallet-balances, with the chain and token in the new optional balance field of custom sources (walletBalanceSourceSchema); ingestion refuses the namespace, and a project and environment can have at most 5 such trackers (limit-reached).

    management.trackers.create and Console New tracker take { kind: "wallet-balances", chain, token } (managedTrackerSourceSchema), and domino trackers create --source wallet-balances takes --chain and --token. The new token-hold-streak snippet rewards holding a token for 7 days in a row.

1.56.0

Minor Changes

  • 9ec9f2f: Publishing creates the groups code names. A deployment whose quest groupMembership(group) reward or inGroup(group) prerequisite, offer inGroup(group) requirement, leaderboard group or referral inviterGroup names a group the environment doesn't have now creates it, with no members and the defineGroup handle's title (the key when only a leaderboard or referral program names it), instead of refusing with 422 validation-failed. Approving a promotion creates the groups live lacks the same way. The deployment preview and result, and the prepared promotion, list each such group as a group-created warning, and the audit history records group-created with the deployment or promotion as its reason. An existing group keeps its title and members, and publishing never unarchives one: a groupMembership reward naming an archived group still refuses. A project whose code uses groups no longer needs domino groups create before its first deployment or promotion.
  • fbe8e64: Offers take prerequisites. defineOffer({ ..., requires }) accepts the prerequisites quests do, completed(quest), balanceAtLeast(ledger, minimum) and inGroup(group), at most 30; offerDefinitionSchema and offerAuthoringSchema gain an optional requires. Publication refuses a required quest or ledger the deployment doesn't know. Redeeming as a member who misses one fails with 409 prerequisites-incomplete and missing, the shape quest admission uses, before any points or stock are spent. Offer gains requires and missing: for a signed-in member, relay.offers.list() and useOffers() list the prerequisites they have not met, empty for a read without a member or an offer without requires. availability keeps its four kinds. Console Rewards shows an offer's requirements on its card, and a promotion carries them to live.
  • cba70f4: Add the organization's billing read. management.billing.organization() and domino billing organization (GET /management/v1/organization/billing) are open to any member of the organization and need no project. They list the projects the caller may read, each with its plan, live and liveState as management.usage.plan() reports them, and its Stripe subscription. They also return the organization's Free projects against their limit (freeProjects), the free AI allowance its Free projects share (freeAi), the monthly AI allowance across the organization, the trial, whether plan limits are enforced, and whether the caller may open the customer portal (billing). management.billing.organizationPortal() (POST /management/v1/organization/billing/portal) opens the customer portal as management.billing.portal() does, returning to Console's organization billing page. organizationBillingSchema and the OrganizationBilling type are exported from @domino-sdk/relay/server, with a reader in managementResponses.
  • 6458ebe: Add the project's plan read. management.usage.plan() and domino usage plan (GET /management/v1/usage/plan, read permission) return the project's plan and where it came from (source), whether it may serve live and why (live), live's notice or offline state (liveState), the organization's one live trial (trial), whether the plan includes a custom domain, the AI builder balance by pool with when it renews (ai), the Stripe subscription as management.billing.read() reports it, and whether plan limits are enforced. Unlike management.billing, it is open to anyone who can read the project. projectPlanSchema and the ProjectPlan type are exported from @domino-sdk/relay/server, with a reader in managementResponses.
  • 62c3791: Add TikTok creator accounts and the tiktok.videos() tracker source. A signed-in member links their TikTok account through TikTok's consent screen with relay.auth.creatorAccounts.connect("tiktok") (useCreatorAccounts() in React); the account's subject is its open_id, its profile a display name and avatar, and knownCreatorAccountProviders now lists "tiktok". Operators read and unlink it like a YouTube channel. tiktok.videos() from @domino-sdk/relay/authoring reads the public videos on the accounts members linked, only with each member's own token, every refreshSeconds of the tracker, as content with a TikTokVideoBody (title, description, durationSeconds, width, height, createdAt, shareUrl, isAigc) and the metrics views, likes, comments and shares, so content() quests, performance and engagement rules use it. A known video the account no longer returns is a content removal; an account whose token needs reconnecting is not read, as provider-unavailable. It is a custom source on the reserved namespace tiktok-videos, which ingestion refuses. Both are inert until Domino configures TikTok. The tiktok-videos capability and the participant and authoring references describe them.
  • a533f93: Add creator accounts and the youtube.shorts() tracker source. A signed-in member links the YouTube channel they publish from as a creator account: relay.auth.creatorAccounts.list() (GET /v1/auth/creator-accounts), connect(provider) and disconnect(provider), and useCreatorAccounts() in React. A creator account is never a sign-in method, linking another channel replaces the current one, and the account's subject is the channel ID picked on Google's consent screen. Each entry mirrors AccountConnection with the channel's profile; provider is an open string, with knownCreatorAccountProviders in @domino-sdk/relay/schema listing "youtube", so CreatorAccount, CreatorAccounts and their schemas sit beside SDK 1.x's closed provider lists. Operators read them on management.members.get(member).creatorAccounts and management.members.creatorAccounts(member), and management.members.unlinkCreatorAccount(member, { provider, subject, revision, reason }) unlinks one with review permission; managedCreatorAccountSchema, managedCreatorAccountsSchema and memberCreatorAccountUnlinkInputSchema are exported from @domino-sdk/relay/server. youtube.shorts() from @domino-sdk/relay/authoring reads the Shorts on the channels members linked, every refreshSeconds of the tracker, as content with a YouTubeShortBody (title, description, tags, durationSeconds, publishedAt) and the metrics views, likes and comments, so content() quests, performance and engagement rules use it. It is a custom source on the reserved namespace youtube-shorts, which ingestion refuses. Both are inert until Domino configures YouTube. The creator-accounts and youtube-shorts capabilities and the participant and authoring references describe them.

1.55.0

Minor Changes

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

    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.

  • 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.

    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.

  • 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.

    EvaluationMember is now @compat sealed: Relay and the testing kit create it, and a 1.x minor release can add members to it.

    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.

  • 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.

    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.

    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.

  • 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.

  • 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.

    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.

    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.

  • 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.

  • 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.

    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.

  • 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.

  • 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.

  • 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.

    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.

    The starters use questActionState instead of their own copies of these rules, and the classic starter's server/participant.ts gives way to signedInMember.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

    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.

  • 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.

Patch Changes

  • 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.

1.54.0

Minor Changes

  • 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.

    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.

  • 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.

    • 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.
    • management.domains.list(), .get(id), .add(hostname), .recheck(id) and .remove(id).
    • Root domains, wildcards, IP addresses and Domino's own domains are refused with invalid-hostname. A project has one custom domain.
    • The hosted-custom-domains capability is now supported.
  • 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.

  • 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.

1.53.1

No changes in this release.

1.53.0

Not published to npm. Its changes first reached npm in 1.53.1.

Minor Changes

  • 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.

1.52.1

No changes in this release.

1.52.0

Minor Changes

  • 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.

1.51.0

Minor Changes

  • 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).

1.50.0

Minor Changes

  • 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.

    • 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].
    • 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.
    • 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 }.

    Both need publish permission in the test environment, like the other test-member methods; live refuses them.

1.49.1

No changes in this release.

1.49.0

Minor Changes

  • 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.

  • 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".

    • 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.
    • A CLI sign-in session can set and delete app secrets in live. Other live writes still need Console or an API key.
    • 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.
    • The hosted-live-app capability is now supported.

1.48.0

Not published to npm. Its changes first reached npm in 1.49.0.

Minor Changes

  • 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.

1.47.0

Not published to npm. Its changes first reached npm in 1.49.0.

Minor Changes

  • 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.

    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.

1.46.0

Minor Changes

  • 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.

    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.

1.45.0

Minor Changes

  • 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.

1.44.0

Minor Changes

  • 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.

Patch Changes

  • 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.

1.43.0

Minor Changes

  • 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.

    • 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].
    • 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.
    • Tracker record reads mark these records synthetic: true. Collected and ingested records never carry it.
    • 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.

    It needs publish permission in the test environment, like the other test-member methods; live refuses it.

1.42.0

Minor Changes

  • 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.

1.41.0

Not published to npm. Its changes first reached npm in 1.42.0.

Minor Changes

  • 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.

1.40.0

Not published to npm. Its changes first reached npm in 1.42.0.

Minor Changes

  • 68bf9eb: Promote test to live as one reviewed request (ADR 0017). 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.

    • 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.
    • domino promote --json prepares one from test; domino promotions list and domino promotions get read them.
    • 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.

    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.

    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.

1.39.1

Patch Changes

  • 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.

1.39.0

Minor Changes

  • 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.

1.38.1

Patch Changes

  • 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.

1.38.0

Minor Changes

  • 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.

1.37.1

No changes in this release.

1.37.0

Not published to npm. Its changes first reached npm in 1.37.1.

Minor Changes

  • 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.
  • 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.
  • 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.
  • 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.

1.36.0

Minor Changes

  • 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.

1.35.0

Not published to npm. Its changes first reached npm in 1.36.0.

Minor Changes

  • 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.

    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.

1.34.0

Minor Changes

  • 81d0037: Definitions change in test and reach live only through a promotion (ADR 0017). In live, every definition write fails with 422 promotion-required, whatever the credential, Console sessions included. These 1.x management routes refuse live:

    • POST /releases, /releases/batch, /releases/settings, /releases/lifecycle, and /quests/:quest/archive and /unarchive
    • POST /catalog/deploy, /catalog/quests, /catalog/drafts and /catalog/publish
    • POST /engagement/publish and /engagement/change
    • POST /point-ledgers, /point-ledgers/update, /point-ledgers/:ledger/update, /archive and /unarchive
    • POST /calendar and /profile-settings
    • POST /badges, /badges/:badge/update, /archive and /unarchive
    • POST /pickup-inventory and /leaderboards/:board/exclusion-group
    • POST /stock-items, /stock-items/:item/rename, /archive and /unarchive
    • POST /resources/lifecycle, and /archive and /unarchive under /trackers, /referral-programs, /leaderboards, /engagement/rules, /collections, /quest-types and /images

    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.

    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".

1.33.0

Minor Changes

  • 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.

1.32.0

Minor Changes

  • 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.

    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.

1.31.0

Minor Changes

  • 07802d2: Reward deliveries, redemptions and deployments page by cursor and nextCursor, so long histories no longer stop silently.

    • 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.
    • 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.
    • 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.

    The deprecated forms keep working through 1.x and are removed in 2.0.0.

Patch Changes

  • 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.

1.30.1

No changes in this release.

1.30.0

Minor Changes

  • 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.

1.29.0

Minor Changes

  • 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.

1.28.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 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.

    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.

    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.

1.27.1

Not published to npm. Its changes first reached npm in 1.29.0.

No changes in this release.

1.27.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 072493d: Leaderboard exclusions are a group (DOM-205):

    • 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.
    • 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.
    • 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.
    • 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.
    • CLI: domino leaderboards exclusion-group BOARD and domino leaderboards bind-exclusion-group BOARD GROUP --reason ….

1.26.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 91d7ad0: Relay keeps quest submissions (DOM-268). See ADR 0014.

    • 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.
    • 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.
    • 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.
    • 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.

1.25.1

Not published to npm. Its changes first reached npm in 1.29.0.

No changes in this release.

1.25.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 55d08ae: Client SDK paper cuts (DOM-160):

    • 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.
    • 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.
    • 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.
    • 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.
    • paginate(read) from @domino-sdk/relay iterates every item of a paged read with for await, following nextCursor.
    • RelayErrorCode lists OperationRetired (operation-retired) and Credential (credential).
    • 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.
    • 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).

1.24.1

No changes in this release.

1.24.0

Minor Changes

  • 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>.

    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.

1.23.2

No changes in this release.

1.23.1

Patch Changes

  • 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.

1.23.0

Minor Changes

  • 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.

    • 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.
    • 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.
    • Text settings can carry picker, which Console uses to offer a list.
    • The telegram-membership snippet is a join quest.

1.22.0

Minor Changes

  • 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.

1.21.0

Minor Changes

  • 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.

1.20.0

Minor Changes

  • 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.

    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:

    • relay.auth.providers() returns telegram: { login, miniApp }.
    • relay.auth.telegram has start(mode), account(), disconnect(), and miniApp.signIn(initData) and miniApp.connect(initData). telegramMiniAppInitData() reads the Mini App's launch data.
    • 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.
    • 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.

1.19.0

Minor Changes

  • 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.

1.18.1

Patch Changes

  • 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.

1.18.0

Minor Changes

  • 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.

1.17.0

Not published to npm. Its changes first reached npm in 1.18.0.

Minor Changes

  • 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.

1.16.0

Minor Changes

  • 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.

    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 for custom tracker ingestion and recovery.

1.15.1

Not published to npm. Its changes first reached npm in 1.16.0.

No changes in this release.

1.15.0

No changes in this release.

1.14.0

Minor Changes

  • 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.
  • 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.

Patch Changes

  • 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.
  • 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.

1.13.0

Not published to npm. Its changes first reached npm in 1.14.0.

Minor Changes

  • 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.

    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.

    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.

    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.

    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).

1.12.0

Not published to npm. Its changes first reached npm in 1.14.0.

Minor Changes

  • 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.

1.11.2

Not published to npm. Its changes first reached npm in 1.14.0.

Patch Changes

  • 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.

1.11.1

Patch Changes

  • 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.

1.11.0

Minor Changes

  • 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.

1.10.1

Patch Changes

  • 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.

1.10.0

Minor Changes

  • 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.

    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 for the opt-in API.

1.9.0

Minor Changes

  • 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.

    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.

    See retry after an uncertain answer for helper lifetime, retry behavior, and reload recovery. Existing explicit keys and upload aliases continue to work.

1.8.0

Not published to npm. Its changes first reached npm in 1.9.0.

Minor Changes

  • 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.

1.7.0

Minor Changes

  • 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.

1.6.0

Minor Changes

  • 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.

  • 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 reference lists them.

    • RelayErrorCode adds PrerequisitesIncomplete, QuestClosed, QuestPaused, ProfileLocked, InvitationNotFound, InvalidCode, CodeExpired, ChallengeExpired and RateLimited.
    • New managementErrorSchema (@domino-sdk/relay/server) parses the management API's error body, for raw api.raw() answers.

    Messages keep their text, and code stays an open string, so code written against 1.2 keeps working.

    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.

  • 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.

    • 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.
    • domino groups list|show|create|update|archive|unarchive|members|add|remove does the same from the CLI.
    • Groups are archived, never deleted. An archived group's members still count, but cannot change until it is unarchived.
    • 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 }).

    defineGroup and inGroup are unchanged. See groups.

  • 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.

    • 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.
    • 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.
    • 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.
    • New management.trackers.triggerOutcomes(tracker, { ...filters, cursor }) answers { items, nextCursor } and replaces management.trackers.outcomes, whose before and next stay as they were.
    • New management.engagement.ruleVersions(rule, { cursor }) answers { items, nextCursor } and replaces management.engagement.versions({ rule, before }).
    • resourceOptionsSchema (@domino-sdk/relay/schema) adds nextCursor; its cursor field is deprecated.

    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.

  • 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 }).

    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.

  • e3a050e: Management commands on an existing resource now name it in the path, and the client methods take it as their first argument:

    • 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.
    • management.pointLedgers.update(ledger, { name, description }) calls /point-ledgers/:ledger/update and replaces the deprecated pointLedgers.save({ id, name, description }).
    • 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.

    Duplicate and misleading names have clear replacements; the old names stay as deprecated aliases:

    • management.points.record(input) awards, spends, adjusts, reverses or sets a balance, replacing points.update.
    • management.pointLedgers.list() and pointLedgers.create(input) replace points.ledgers() and points.createLedger(input).

    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.

    The routes that took these IDs in the body keep answering released clients. Deprecated forms are removed in 2.0.0.

  • b145e16: The management client reads a project's state as resources, which Console now composes its pages from:

    • management.metrics.read(): the project's counts and each quest's completions (completionsByQuest).
    • 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.
    • management.attempts.list({ q, status, limit, cursor }) and management.attempts.get(attempt); management.entitlements.list({ q, status, limit, cursor }) and management.entitlements.get(entitlement).
    • management.audit.list({ category, q, limit, cursor }) and, for organization administrators, management.audit.access({ q, limit, cursor }).

    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.

    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.

  • 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.

  • 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.

    The agent references and capability map now list tracker-restricted ingestion keys (ingestTrackers on management.tokens.create and management.tokens.scope) as supported.

  • 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.

Patch Changes

  • 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.

  • 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.

    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.

  • 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.

  • 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.

  • 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).

  • 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.

  • 65a9765: Deprecated three schema members that Relay no longer uses. They stay in 1.x and are removed in 2.0.0:

    • faultAfterReview on submitSchema (SubmitInput). Relay refuses a submission that sets it to true, so stop sending it.
    • 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.
    • 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.

    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.

1.5.2

No changes in this release.

1.5.1

No changes in this release.

1.5.0

Minor Changes

  • 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.

    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.

1.4.0

Minor Changes

  • 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 for record timestamps, replay, and real-time workflow scheduling.

    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.

    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.

1.3.0

Minor Changes

  • 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.

1.2.0

Minor Changes

  • 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.

    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.

  • 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.

1.1.1

Patch Changes

  • 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.

1.1.0

Minor Changes

  • 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.

1.0.0

Major Changes

  • 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.

    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.

  • 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.

  • 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.

    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 in the v1 upgrade guide.

  • 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.

  • 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.

    • 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.
    • Date.now(), new Date() and Date() return ctx.now and do not advance, even across awaited capability calls. performance.now() returns 0.
    • Math.random(), crypto.getRandomValues() and crypto.randomUUID() repeat the same sequence for one attempt and differ between attempts.

    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 in the v1 upgrade guide.

  • 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.

    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.

    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.

    To upgrade, see Observation triggers become tracker triggers and The observe permission is replaced by enroll.

  • 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.

    See Engagement budgets are a list of periods in the v1 upgrade guide.

  • 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.

  • 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.

    Upgrade steps: Content stages.

  • 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.

    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.

    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.

    Published tracker definitions in status, list, record-page, summary, and deployment responses use trackerDefinitionResponseSchema and TrackerDefinitionResponse. Authoring keeps the strict trackerDefinitionSchema and TrackerDefinition.

    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.

    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().

  • 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.

  • 54fcf41: The participant client follows one convention for paging, sessions and errors.

    • 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.
    • 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).
    • 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.
    • 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).
    • 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.
    • Hosted proxies no longer forward the 0.x single-wallet routes (/v1/me/wallet, /challenge, /verify).

    Upgrade: One paging, session and error convention in the participant client in the SDK v1 upgrade guide maps every method and has search commands.

  • 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 in the v1 upgrade guide.

  • 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.

    • 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.
    • streak() and calendar leaderboard windows no longer take a timeZone; both throw if given one. Streaks and boards already published with one still read.
    • Budgets reset at midnight in the project timezone instead of UTC, and accept period: "month".
    • 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.

    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 in the v1 upgrade guide.

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

  • c919948: Follow-ups to the React hooks' shared query layer.

    • completionCountLabel(view) is renamed completionCountText(view), so the name says it returns English like questStatusText. For your own words, read view.completionCount.
    • 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.
    • 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.
    • useSignOut() drops the signed-out member's cached values as soon as Relay confirms the sign-out, even when the next session read fails.
    • A useMyProfile() change that answers after another member signed in no longer writes into the new member's cache.
    • 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.
    • progressOf(quests) is documented: total counts locked and paused quests and leaves out archived and repeatable ones.

    Upgrade: React hooks share one cache and return one state shape in the SDK v1 upgrade guide.

  • c2390b3: The React hooks in @domino-sdk/relay/react share one query cache and return one state shape.

    • 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.
    • <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.
    • 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().
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.
    • completionMark, hasPending, sanitizeDeliveries, watchRoleDeliveries, PENDING_POLL_MS, RETRY_DELAYS_MS, runsSilently and visibleQuests are no longer exported from @domino-sdk/relay/react.
    • 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.

    Upgrade: React hooks share one cache and return one state shape in the SDK v1 upgrade guide maps every hook.

  • 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.

    • Zod schemas (and helpers such as identifier, caip10 and xPostId) come from @domino-sdk/relay/schema.
    • 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.
    • avatarSrc, defaultAvatar, defaultAvatarSvg and walletAvatarSeed come from @domino-sdk/relay/avatar, and no longer from @domino-sdk/relay or @domino-sdk/relay/react.
    • 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.
    • No longer exported: questActions and reconcileQuestDraft. Open a quest controller with relay.quests.open(view); its quest-updated result already carries the reconciled draft.
    • The package declares sideEffects: false.
    • 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.

    Upgrade: Runtime entry exports only the client in the SDK v1 upgrade guide has the import mapping and search commands.

  • 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 before replacing a 0.x installation.

    Stable APIs follow semantic versioning throughout the supported v1 lifetime. Compatibility exemptions apply only to explicitly experimental entrypoints.

  • ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 2 is the first stable contract, and all pre-v1 bundles must be rebuilt and republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See Wallet reads return every linked wallet in the v1 upgrade guide.

  • 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.

    • 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.
    • 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.
    • 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.
    • 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.

    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.

Minor Changes

  • 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.

    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.

    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.

    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.

  • 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. Update custom attempt renderers using the counter upgrade steps.

  • 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.

  • e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose management.webhooks, verifyWebhook, shared activity constants, and domino webhooks commands. This is additive; existing integrations need no changes. See Receive outbound webhooks for an optional polling migration.

  • 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.

    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.

  • 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.

  • 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.

  • 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.

    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.

  • 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.

  • 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.

  • 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.

    The classic starter's header now reads the balance with useBalances() instead of its /api/me/points server route, which is gone.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

  • 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.

    See Performance evaluators no longer select rewards in the upgrade guide.

  • 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.

    See Prerequisites are typed eligibility rules and Authoring references use typed handles in the upgrade guide.

  • 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 before upgrading.

    Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.

    Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.

  • 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.

  • 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the resource lifecycle.

  • ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.

    • 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.
    • 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.
    • 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.
    • A session-bound participant call that reads the current session first shares one deadline with that read, and auth.session() honours timeout.
    • 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.
    • 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.
    • 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.

    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 for the recovery-handler change.

    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 for the recovery-handler change.

  • 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.

  • 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.

    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 for old and new code, capture semantics, and search patterns.

  • 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.

  • 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.

    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.

  • 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.

  • 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.

    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.

    See Custom sources declare their record data and Quest settings come only from settings builders in the upgrade guide.

  • 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.

Patch Changes

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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 in the participant API reference.
  • 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.
  • 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.

1.0.0-next.34

Major Changes

  • 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.

    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.

  • 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.

  • 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.

    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 in the v1 upgrade guide.

  • 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.

  • 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.

  • 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.

    Upgrade steps: Content stages.

  • 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 in the v1 upgrade guide.

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

  • ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 1 remains supported, while protocol-less bundles must be republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See Wallet reads return every linked wallet in the v1 upgrade guide.

Minor Changes

  • 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. Update custom attempt renderers using the counter upgrade steps.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

  • 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.

  • 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the resource lifecycle.

  • 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.

  • 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.

Patch Changes

  • 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.
  • 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.

1.0.0-next.33

Minor Changes

  • 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.

1.0.0-next.32

Minor Changes

  • 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.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

1.0.0-next.31

Minor Changes

  • 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 before upgrading.

    Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.

    Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.

1.0.0-next.30

Minor Changes

  • 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.

    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.

    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.

    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.

1.0.0-next.29

Minor Changes

  • 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.

    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 for old and new code, capture semantics, and search patterns. This ships in the 1.0.0-next prerelease series.

  • 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.

1.0.0-next.28

Patch Changes

  • 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.

1.0.0-next.27

Minor Changes

  • e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose management.webhooks, verifyWebhook, shared activity constants, and domino webhooks commands. This is additive; existing integrations need no changes. See Receive outbound webhooks for an optional polling migration.

1.0.0-next.26

Minor Changes

  • 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.

1.0.0-next.25

No changes in this release.

1.0.0-next.24

Major Changes

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

1.0.0-next.23

Major Changes

  • 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.

1.0.0-next.22

Major Changes

  • 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.

    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.

    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.

    Published tracker definitions in status, list, record-page, summary, and deployment responses use trackerDefinitionResponseSchema and TrackerDefinitionResponse. Authoring keeps the strict trackerDefinitionSchema and TrackerDefinition.

    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.

    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().

1.0.0-next.21

Minor Changes

  • ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.

    • 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.
    • 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.
    • 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.
    • A session-bound participant call that reads the current session first shares one deadline with that read, and auth.session() honours timeout.
    • 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.
    • 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.
    • 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.

    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 for the recovery-handler change.

    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 for the recovery-handler change.

1.0.0-next.20

No changes in this release.

1.0.0-next.19

Patch Changes

  • 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.

1.0.0-next.18

Minor Changes

  • 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.

    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.

1.0.0-next.17

Major Changes

  • c919948: Follow-ups to the React hooks' shared query layer.

    • completionCountLabel(view) is renamed completionCountText(view), so the name says it returns English like questStatusText. For your own words, read view.completionCount.
    • 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.
    • 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.
    • useSignOut() drops the signed-out member's cached values as soon as Relay confirms the sign-out, even when the next session read fails.
    • A useMyProfile() change that answers after another member signed in no longer writes into the new member's cache.
    • 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.
    • progressOf(quests) is documented: total counts locked and paused quests and leaves out archived and repeatable ones.

    Upgrade: React hooks share one cache and return one state shape in the SDK v1 upgrade guide.

1.0.0-next.16

Major Changes

  • 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.

    • 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.
    • 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.
    • 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.
    • 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.

    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.

1.0.0-next.15

Major Changes

  • 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.

    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.

    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.

    To upgrade, see Observation triggers become tracker triggers and The observe permission is replaced by enroll.

1.0.0-next.14

Major Changes

  • c2390b3: The React hooks in @domino-sdk/relay/react share one query cache and return one state shape.

    • 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.
    • <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.
    • 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().
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.
    • completionMark, hasPending, sanitizeDeliveries, watchRoleDeliveries, PENDING_POLL_MS, RETRY_DELAYS_MS, runsSilently and visibleQuests are no longer exported from @domino-sdk/relay/react.
    • 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.

    Upgrade: React hooks share one cache and return one state shape in the SDK v1 upgrade guide maps every hook.

1.0.0-next.13

Major Changes

  • 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.

    See Engagement budgets are a list of periods in the v1 upgrade guide.

  • 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.

    • 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.
    • streak() and calendar leaderboard windows no longer take a timeZone; both throw if given one. Streaks and boards already published with one still read.
    • Budgets reset at midnight in the project timezone instead of UTC, and accept period: "month".
    • 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.

    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 in the v1 upgrade guide.

1.0.0-next.12

Minor Changes

  • 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.

    The classic starter's header now reads the balance with useBalances() instead of its /api/me/points server route, which is gone.

1.0.0-next.11

Minor Changes

  • 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.

    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.

Patch Changes

  • 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 in the participant API reference.

1.0.0-next.10

No changes in this release.

1.0.0-next.9

Minor Changes

  • 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.

    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.

    See Custom sources declare their record data and Quest settings come only from settings builders in the upgrade guide.

1.0.0-next.8

Major Changes

  • 54fcf41: The participant client follows one convention for paging, sessions and errors.

    • 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.
    • 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).
    • 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.
    • 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).
    • 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.
    • Hosted proxies no longer forward the 0.x single-wallet routes (/v1/me/wallet, /challenge, /verify).

    Upgrade: One paging, session and error convention in the participant client in the SDK v1 upgrade guide maps every method and has search commands.

1.0.0-next.7

Major Changes

  • 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.

    • Zod schemas (and helpers such as identifier, caip10 and xPostId) come from @domino-sdk/relay/schema.
    • 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.
    • avatarSrc, defaultAvatar, defaultAvatarSvg and walletAvatarSeed come from @domino-sdk/relay/avatar, and no longer from @domino-sdk/relay or @domino-sdk/relay/react.
    • 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.
    • No longer exported: questActions and reconcileQuestDraft. Open a quest controller with relay.quests.open(view); its quest-updated result already carries the reconciled draft.
    • The package declares sideEffects: false.
    • 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.

    Upgrade: Runtime entry exports only the client in the SDK v1 upgrade guide has the import mapping and search commands.

1.0.0-next.6

Patch Changes

  • 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.

1.0.0-next.5

No changes in this release.

1.0.0-next.4

Minor Changes

  • 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.

1.0.0-next.3

Major Changes

  • 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.

    • 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.
    • Date.now(), new Date() and Date() return ctx.now and do not advance, even across awaited capability calls. performance.now() returns 0.
    • Math.random(), crypto.getRandomValues() and crypto.randomUUID() repeat the same sequence for one attempt and differ between attempts.

    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 in the v1 upgrade guide.

Patch Changes

  • 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.

1.0.0-next.2

Minor Changes

  • 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.

    See Performance evaluators no longer select rewards in the upgrade guide.

  • 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).

    See Prerequisites are typed eligibility rules in the upgrade guide.

Patch Changes

  • 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.
  • 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.

1.0.0-next.1

No changes in this release.

1.0.0-next.0

Major Changes

  • 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.

    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.

    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.

0.31.1

No changes in this release.

0.31.0

Minor Changes

  • 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.

    • 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.
    • 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.
    • 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.
    • 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".
    • 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.

    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:

    • 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.
    • 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.
    • 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.
    • Trigger outcomes always carry code: trackers.outcomes needs this SDK.

    Breaking changes. For code written against the tracker triggers of 0.25.0 to 0.27.0:

    • Replace customSource(namespace, { authors: "wallet" }) with customSource(namespace, { identities: ["wallet"] }). authors stays readable on published trackers and is deprecated.
    • Replace automatic({ tracker, beneficiary: "replied-to" }) with beneficiary: "inReplyTo".
    • TrackerBeneficiary and trackerBeneficiarySchema are removed; the beneficiary is a role string, or "replied-to" on releases published before roles.

    Upgrading. Deploy the backend (migration 0074_tracker_trigger_primitives) before publishing quests that use the new options; older servers refuse them.

0.30.0

Minor Changes

  • 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.

    • 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.
    • 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:: it is not a supported EVM chain. …".
    • 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.

    Breaking. evmChains, evmChainSchema, EvmChain and EvmChainInfo are no longer exported from @domino-sdk/relay.

    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.

  • c6afc57: ingest is a normal management permission: it sends records to custom trackers (management.trackers.ingest) and allows nothing else.

    • Editors hold ingest, and organization administrators hold every permission, so management.access() lists it for them. rolePermissions.editor includes it.
    • 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.
    • Hosted-app keys (dhk_) now list ingest in the minted key, the development key and access(), alongside read and points. They always held it.
    • 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").
    • domino dev needs your read, points and ingest permissions for its development key; editors have all three.
    • tokenSchema (the personal API key list) drops permissions this release does not know instead of rejecting the list, as access() already does.

    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.

0.29.0

Minor Changes

  • 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.

    • relay.profiles.get(id) returns the profile only: id, name and avatar (profileSummarySchema).
    • 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.
    • 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.
    • 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.
    • React: useLeaderboardPlacement(board, id), useProfileCompletions(id, { limit }) with loadMore(), and useProfileReferral(id, program). useProfile(id) returns the profile only.
    • Profile settings: defaults is an ordered list of the accounts that fill in names and pictures, "x" and "discord" ([] for generated). profileDefaultProviderSchema lists them.
    • 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.
    • proxyParticipant and the API's scope selector admit the new routes (isProfileParticipantRoute).
    • 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.

    Upgrading. Breaking for apps, servers and CLIs on 0.25 to 0.27 that use profiles:

    • 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.
    • 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.
    • 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.
    • 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 [].
    • 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.
    • 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.
    • 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.

0.28.0

Minor Changes

  • 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.

    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.

    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.

    Management member accounts now carry kind: wallet for wallet identities and account for the rest. Responses from older servers read as account.

0.27.0

Minor Changes

  • 9ef2629: Cosmos wallets work on any Cosmos SDK chain your app chooses. Relay no longer ships a list of Cosmos chains.

    • 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.
    • 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.
    • 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.
    • React: useWalletDiscovery({ cosmos }) and useSignInWallets(families, { cosmos }) take the app's chains (one or an array).
    • 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.
    • 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.

    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.

    • 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.

    • 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.

    • 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:

      import type { CosmosChain, WalletSignInNetwork } from "@domino-sdk/relay"
      
      /** The Cosmos SDK chains this app offers wallet sign-in on. */
      export const COSMOS_CHAINS: CosmosChain[] = [{ chainId: "cosmoshub-4" }]
      
      const NETWORK_NAMES: Record<WalletSignInNetwork, string> = {
        solana: "Solana",
        eip155: "EVM",
        cosmos: "Cosmos",
      }
      
      // In the sign-in component:
      const wallets = useSignInWallets(methods?.wallets, {
        cosmos: COSMOS_CHAINS,
      })
    • cosmosNetworks is removed, and CosmosNetwork is any cosmos:<chain ID>. Pass your chain to the adapters instead of looking it up.

    • useWalletDiscovery() no longer lists Cosmos wallets on a default chain: pass { cosmos: { chainId: "…" } }. useSignInWallets(networks) lists Cosmos wallets only with { cosmos }.

    • WalletSignInNetwork is now "solana" | "eip155" | "cosmos"; a SignInWallet's network is its family, and a Cosmos wallet's chain is signer.network.

0.26.0

No changes in this release.

0.25.1

No changes in this release.

0.25.0

Minor Changes

  • 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.

    • 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.
    • React: useMyProfile() and useProfile(id); avatarSrc(avatar) renders any profile avatar. Generated avatars use the same seed as relay.me.get().avatarSeed.
    • 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).
    • 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.

    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.

  • 531f41e: Members can link several Solana, EVM and Provenance wallets to a project and unlink them.

    • relay.wallets.list(), link(signer), unlink(wallet), and the lower-level challenge() and verify().
    • Dependency-free signer adapters and discovery in @domino-sdk/relay/browser: evmSigner, solanaSigner, walletStandardSolanaSigner, cosmosSigner, discoverEvmWallets, discoverSolanaWallets, discoverCosmosWallets.
    • React: useWallets() and useWalletDiscovery().
    • Evaluators: ctx.wallet.list() returns the member's linked wallets, pinned once per attempt.
    • Management: unlinkMemberWallet(member, { chain, address, reason }) (requires review), and wallets in the member detail.
    • Contract helpers: caip10(), cosmosNetworks, isWalletParticipantRoute(), and the LinkedWallet, WalletSigner and related schemas.

    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.

  • c71712f: Quests can trigger from custom tracker records attributed to members' verified wallets.

    • 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.
    • 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.
    • management.trackers.outcomes(tracker, query) explains each admission or skip with its reason; management.trackers.retryOutcome(tracker, input) (requires review) retries a skipped admission.
    • management.trackers.records(tracker, { member }) works on wallet trackers, returning the member's linked wallets' records. trackers.ingest results include admissions counts.
    • New ingest permission, granted to hosted-app keys: it allows custom tracker ingestion only. People keep ingesting with observe.

    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.

  • ea5d69e: Quests can check token balances of a member's linked wallets.

    • 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.
    • Authoring: parseUnits() and formatUnits() convert whole-token amounts, and WalletBalanceQuery and WalletBalance type the call.
    • Contract: evmChains, evmChainSchema and walletBalanceSchema.
    • Management: the member detail's balanceChecks lists the member's recent recorded balance reads; Console shows them under the member's wallets.
    • Snippet: token-holder checks that a member's linked wallets hold at least a minimum.

    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.

  • 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.

    • relay.auth.wallet.signIn(signer), and its two steps start(input) and verify(proof). relay.auth.providers() returns wallets, the networks the project enabled.
    • 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.
    • 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.
    • Contract: walletSignInNetworkSchema, walletSignInNetwork(), isWalletSignInRoute() and memberSummarySchema.
    • 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.

    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.

  • b22ead4: X trackers can trigger quests, and budgets can reset weekly.

    • 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.
    • 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 }.
    • budget({ period: "week" }) limits completions or points per ISO week in UTC, starting Monday, by the attempt's eligibility time.
    • 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".

    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.

0.24.0

No changes in this release.

0.23.1

No changes in this release.

0.23.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • 49fa0fd: Apps can now match the ledger and tiered reward IDs that reads return to the keys they declared.

    • relay.pointLedgers.list() (GET /v1/point-ledgers) and the React hook usePointLedgers() return each ledger's id, key, and name.
    • relay.tieredRewards.list() (GET /v1/tiered-rewards) and useTieredRewards() return each tiered reward's id and key.
    • 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.

    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.

    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.

    Upgrade notes:

    • 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.
    • TypeScript code that builds quest views, such as test fixtures, must add kind and that kind's fields.
    • 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.
    • No database migration is required.

0.22.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • 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.

    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".

0.21.0

Not published to npm. Its changes first reached npm in 0.23.1.

No changes in this release.

0.20.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • 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.

    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.

0.19.0

No changes in this release.

0.18.4

No changes in this release.

0.18.3

No changes in this release.

0.18.2

No changes in this release.

0.18.1

Patch Changes

  • 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".

    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.

    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.

    Upgrade note: if relay.json apiUrl is set to a management proxy URL, change it to the participant API origin (https://relay.domino.run).

0.18.0

Minor Changes

  • 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.

    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.

  • 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.

  • 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.

    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.

  • 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.

    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.

  • 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.

    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.

    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.

  • 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.

    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.

  • 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.

    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.

    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.

  • 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.

    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.

    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.

    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.

  • 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.

0.17.2

Patch Changes

  • 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.

    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.

    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.

0.17.1

No changes in this release.

0.17.0

No changes in this release.

0.16.1

No changes in this release.

0.16.0

Not published to npm. Its changes first reached npm in 0.16.1.

Minor Changes

  • 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.

    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.

  • 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.

    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.

    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.

  • 074c292: Add captured X verification to authored quest evaluators: x.connection(), x.profile(), x.post({ postId }), x.follows({ userId }), and x.reposted({ postId }).

    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.

0.15.0

Minor Changes

  • 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.

    @domino-sdk/relay/react provides the React layer the starters share:

    • RelayProvider, useExperience, questGroups, useQuest, and the quest status helpers
    • useSignIn, useSignInMethods, useSignOut, and useAccounts
    • useLeaderboard, useReferralSummary, useInvitation, and useRelay

    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.

    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.

0.14.1

No changes in this release.

0.14.0

Minor Changes

  • 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.

    Requires the matching backend deployment. Existing clients and manifests are unaffected.

0.13.0

Minor Changes

  • 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.

    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.

0.12.0

Minor Changes

  • 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.

0.11.0

Minor Changes

  • 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.

    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.

    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.

0.10.0

Minor Changes

  • 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.

Patch Changes

  • 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.

0.9.1

No changes in this release.

0.9.0

Minor Changes

  • 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.

    New release and catalog publication accept only Workers AI. Historical provider identifiers remain readable for retained records, but cannot select simulated evaluation.

0.8.0

Minor Changes

  • 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.

0.7.0

Minor Changes

  • 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.

0.6.0

Minor Changes

  • 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.
  • 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.
  • 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.

0.5.0

Minor Changes

  • 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.

  • 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.

    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.

  • 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.

    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.

0.4.0

Minor Changes

  • 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.
  • 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.
  • 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.

Patch Changes

  • c475d40: Use the complete non-quest deployment schema for hosted build artifacts, preserving leaderboard and referral modules during preview activation and starter verification.
  • 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.

0.3.0

Minor Changes

  • 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.

0.2.0

No changes in this release.

0.1.0

Minor Changes

  • d46d47c: Introduce the Relay SDK, Domino CLI with bundled agent skills, and standalone local runtime.

CLI

1.66.1

Patch Changes

  • d77e195: Repeated refused credentials and unknown paths from one address are now refused with 429. After an API server has refused one address (an IPv4 address or IPv6 /64) 60 times within 10 minutes for a missing or refused credential, a Git path that names no repository, or a path the API does not serve, it answers that address's management API, MCP and Git requests and unknown paths with 429 rate-limited, message "Too many refused requests. Try again later.", and Retry-After until the 10 minutes end, before checking any credential. A personal key or MCP token counts only when Clerk does not recognize it. Successful requests, Git's first credential-less request of a fetch or push, an MCP request without an MCP token, and a hosted app's own server calls never count. The CLI reports the wait as details.retryAfter; the troubleshooting reference explains how to recover.
  • @domino-sdk/relay@1.66.1

1.66.0

Minor Changes

  • 165ca99: Harder sign-in.

    • domino login codes now expire after 15 minutes, not an hour. Console's approval page shows when the request was made, the country of the IP it came from and the CLI's version and OS, and warns to approve only a code from your own terminal. The CLI sends that description as its User-Agent. One client network (an IPv4 address, or an IPv6 /48) holds at most 20 pending sign-ins.
    • Email sign-in codes name the app. The email says which app and host the code is for, and to enter it only there. Only the latest code for an address works: an earlier one answers code-expired. Ten wrong codes for an address on a project in an hour, or twenty in a day, refuse emailVerify with rate-limited and Retry-After until the window ends, whatever new codes it gets. An address gets at most ten codes an hour from every app together; past that, emailStart answers rate-limited.
    • Plugin OAuth connections finish in Console, as the person who started them (experimental). The vendor's callback no longer connects by itself: it sends the browser to Console with a one-time oauthCode, and the new management.connections.completeAuthorization(plugin, { code }) redeems it within five minutes, for the same person, project and environment only (403 scope-mismatch for anyone else, 409 oauth-flow-expired for a spent or expired code). Without returnTo, the browser returns to Console's Integrations page for the project. authorize now refuses automation, service and hosted app keys with 403 credential-not-allowed. New experimental exports: connectionAuthorizationCompleteInputSchema and ConnectionAuthorizationCompleteInput.

Patch Changes

  • Updated dependencies [165ca99]
    • @domino-sdk/relay@1.66.0

1.65.0

Minor Changes

  • 9a37fe5: Clear test data in hosted app storage. domino storage clear sql DB --table tickets (or --all-tables --keep settings) empties SQL tables and keeps their schema and d1_migrations; domino storage clear kv PHOTOS --prefix photo: (or --all-keys) deletes KV keys. --dry-run counts first, and without --yes the command asks in a terminal and refuses unattended. The management API has the same operation as POST /management/v1/storage/{kind}/{name}/clear, and createManagementClient as storage.clear(kind, name, input). It works only in the test environment: live refuses with test-environment-required. It needs publish, and each clear is audited. A KV clear deletes at most limits.apps.storageClearKeys (10,000) keys a call and answers complete: false while keys remain; the CLI repeats until none do. Test member reset, purge and erase still leave app storage alone.

Patch Changes

  • fabbf60: domino doctor labels a Worker app's preview by its kind. When relay.json declares app.kind: "worker", the --json result now has preview: "worker-app-configured" instead of "static-app-configured". Static apps and projects without an app keep the value they had.
  • 7820fec: The Domino skill's campaign testing reference explains Hide toolbar in the staging toolbar's ⋯ menu: it hides the toolbar for the browser tab, leaving a small restore button in the corner, and while it is hidden --domino-staging-toolbar-inset-bottom is 0px and <html> has no data-domino-staging-toolbar, so screenshots and layout checks see the live layout.
  • a09e24b: The skill's schedule dispatch guarantees say what a run's error now records. A run whose build has no Worker fails, so the next tick still runs. An uncertain run names what Relay saw: the dispatch bridge's HTTP status and cause, no answer within the timeout, or the error that interrupted the send. An exception the app throws before answering is recorded there too, trimmed to 300 characters, so keep secrets out of thrown messages.
  • Updated dependencies [9a37fe5]
    • @domino-sdk/relay@1.65.0

1.64.0

Minor Changes

  • 0a4d580: Domino now deletes the evidence members submit (photos, text, links and quiz answers) 90 days after the attempt is decided, in test and live, unless you change it. Nothing was deleted when this shipped: evidence that already existed is first deleted 90 days after the change reached Domino, on 18 January 2027 for projects that existed on 20 October 2026, and 90 days after creation for newer projects. Attempts waiting for a decision keep their evidence. Each project's date shows in Console's project settings and in noticeEndsAt.

    To keep evidence longer, or until you delete it, change the period in Console's project settings, with management.attempts.setEvidenceRetention({ evidenceRetention: { days } }) (1 to 3650 days) or { evidenceRetention: "keep" }, or with domino attempts set-evidence-retention --days N or --keep. Changing it needs publish; shortening it, or switching from keep to days, also needs an organization admin or a service key. management.attempts.evidenceRetention() and domino attempts evidence-retention read it. Both methods are experimental. Each change is recorded in the project's access history, and each deletion pass as an evidence-redacted-before event with no actor and no quest. To delete sooner, or per quest, keep using attempts.redactEvidenceBefore.

  • 0b1f8de: Operators can ban a member, and optionally take back what they earned. While banned, a member's attempts, completions and invitation acceptances are refused with 403 member-banned, their invitation code answers invitation-not-found, attempts waiting for a decision are rejected, tracker records that would count for them are skipped with member-banned, leaderboards and settlements leave them out, and referral payouts to and from them stop. relay.me.get() now answers eligible, false while the member is banned (true from older Relay versions).

    management.members.ban(member, { reason, rollback }) bans, where rollback can revoke all their completions, reverse the referral payouts they received, and reverse manual awards, imported opening balances and engagement payouts, a step at a time in the background. management.members.previewBan(member, { rollback }) previews it per point ledger, and management.members.unban(member, { reason, restoreCompletions }) lifts it. Banning and unbanning need review permission; reversing referral payouts or point awards also needs publish. members.get answers the member's latest ban as ban, and member lists answer bannedAt. The CLI adds domino members ban (with --rollback and --dry-run) and domino members unban. Member bans are experimental. Member imports refuse rows naming a banned member with member-banned.

Patch Changes

  • 166254b: App login now works on a private staging address. createParticipantBackend no longer sends X-Relay-App-Origin with its proxied calls, and Relay selects a request that carries an app login key by the key even when it also names an app origin, so a Test key registered for https://<slug>--staging.domino.page signs members in there without a workaround. Apps on SDK 1.63.0 work without upgrading. The staging-access-required error reference says a request with an app login key needs no staging pass.
  • 28acc4c: The management skill reference says Console's Integrations page shows a card per plugin that opens a panel with its connection form.
  • 59cf5db: management.identityIntegrations.createIntoAppSecret(input, name) and addSecretIntoAppSecret(id, name) create an app login key, or add a secret to one, and store the new secret as the hosted app secret name in the key's environment instead of returning it. The response has appSecret and no token. They need publish permission, and like every app login key change they work only from an organization administrator's Console session. When the app secret is refused, no key or secret is created. The CLI skill reference and capabilities describe Console's matching "Also save it as an app secret" option.
  • 15b249d: Agents can act as test members on a domino dev preview, before staging. POST /management/v1/staging/link takes an optional preview, the preview URL domino dev printed, and returns a one-minute link on that preview that opens the test toolbar for the same operators as a staging link. The session works only on that preview host and only while it runs; the dev server never receives its cookie, and other visitors to the preview see no change. A URL that is not a running preview of the project answers 404 preview-not-found. The Domino skill's campaign testing reference adds the steps under "Check participant flows on staging", and the hosted reference points to them for checking the participant flow before staging.
  • Updated dependencies [166254b]
  • Updated dependencies [ff6c976]
  • Updated dependencies [0a4d580]
  • Updated dependencies [59cf5db]
  • Updated dependencies [7bd9a79]
  • Updated dependencies [0b1f8de]
    • @domino-sdk/relay@1.64.0

1.63.0

Minor Changes

  • 8c4b0c0: domino checkout accepts a project's slug, as domino whoami --json lists it, as well as its ID; a slug two organizations share needs --organization, and an unknown project now fails with SCOPE_NOT_FOUND before any repository request. Without --json, checkout shows Git's fetch progress, and a fetch that moves under 1 KB/s for 60 seconds fails with CHECKOUT_GIT_FAILED instead of waiting on a dead connection. Without --json, an invalid relay.json now lists each invalid field and its message under the error, as domino doctor does; the --json error is unchanged.

  • 052744c: inspectIntegrationKey({ baseUrl, integrationKey }) from @domino-sdk/relay/portal-proxy answers which app login key a di_ key is: its id, name, organization, project, environment, origin, identityKind, vouchesAccounts, secretExpiresAt and appOrigins, the addresses Domino serves the project's app from in that environment. It calls the new GET /v1/auth/integration, never returns a secret, and does not count as a use of the key. An unknown, expired or revoked key throws ParticipantExchangeError with 401 invalid-credential. identityIntegrationKeySchema and the IdentityIntegrationKey type are exported from @domino-sdk/relay/server.

    Behavior change: domino secrets set now checks a value that starts with di_ before storing it. It refuses, storing nothing and exiting 2 with INVALID_ARGUMENT, a key that is unknown, expired or revoked, or that belongs to another project or environment, such as a Live key set as Test's secret. It stores a key whose origin is not the app's own address for that environment with a warning, and a key it could not check (network failure, or a Relay without the route) with a warning. A checked key is named in the result's new identityIntegration field. Other values are stored as before.

  • eaaae2a: Test members that reset refuses because they redeemed an offer, hold a reservation or a started reward handover, or took part in a referral can now be purged: management.testMembers.purge({ actionId, members, reason }) and domino test-members purge MEMBER... --reason TEXT delete up to 500 test members in one transaction, in the test environment only. The stock unit each redemption, reservation and pickup handover took goes back to its stock item, so the offer's stock and redeemed count stay consistent, and the members' referrals and referral accounting go with them. List both sides of every referral: one with a member outside the list refuses the call with test-member-reset-blocked (referrals affect another member, naming the unlisted member in outside). Every other reason reset refuses still applies, and a refused purge changes nothing. The audit records one event with the operator, the members, the reason and the stock returned. reset is unchanged. See Purge members reset refuses.

  • 69e017d: Erase a member on the person's request: management.members.erase(member, { reason, actionId? }) and domino members erase MEMBER --reason TEXT [--yes] (POST /management/v1/members/:member/erase, publish permission, live and test). It can't be undone. The member's profile and uploaded avatar, sign-in identities, linked accounts and their tokens, app identities, wallets, sessions, consent, leaderboard aliases, referral code, the evidence of every attempt and the stored content of the tracker records their accounts authored are deleted; reasons people typed on the rows that stay read [erased]. The member ID keeps its points, completions, rewards and referrals. The result is { member, erasedAt, deleted }; while an attempt waits for a decision or a reward handover is in progress it is refused with 409 member-erase-blocked. See Erase a member on request.

    Member reads (members.get, members.list) carry a new erasedAt on an erased member, and members.get answers profile: null for it. After erasure, writes naming the member (points, badges, groups, campaigns, consent, profile changes) are refused with the new 409 code member-erased, records naming its ID attribute to no one, and the same person signing in again becomes a new member. A member import's revert passes over an erased member's row and reports it in keptLinks with reason member-erased. The CLI command asks for confirmation in a terminal and needs --yes otherwise.

  • bbc3623: Klaviyo is the first destination plugin: connected through Klaviyo's OAuth, it keeps opted-in members' Klaviyo profiles current with domino_ properties (points, ranks, quests completed, unredeemed rewards, campaigns), subscribes and unsubscribes them as their email consent changes (a backfill or resync never resubscribes someone Klaviyo has unsubscribed), and records Domino's events as Domino … metrics. It never calls Klaviyo's send, campaign or flow APIs. See the new guide, "Send reminders from Klaviyo".

    management.connections gains, experimental like the rest of it:

    • connect(plugin, { config, dryRun: true }) connects a destination that supports a dry run, in the test environment, without secrets or the vendor's account. It records what it would have sent instead of sending it. Connection.dryRun and PluginInfo.dryRun say which. Refusals: 409 dry-run-not-supported, dry-run-test-only.
    • activity(plugin) reads a destination's delivery record: last sync, failing state, members synced and skipped per reason, the latest backfill or resync with its progress, and the latest 20 errors.
    • resync(plugin) sends every member's profile and attributes again, never past events; one runs at a time.
    • dryRunLog(plugin, { cursor, limit }) pages through a dry run's requests, newest first.

    These answer 409 not-a-destination for other plugins. The CLI adds domino connections connect --dry-run, connections activity, connections resync and connections log.

  • 9cb68e2: A text setting can be secret: settings.text({ label, secret: true }). Operators set its value in Console or with management.quests.updateSettings, and the quest's code reads it as usual, but no management response returns it. Releases leave secret settings out of values and configuration, and list the ones that hold a value in the new optional withheldSettings. So domino deploy --json, --preview --json, quests list, releases get, deployments, attempts and member reads no longer print a shared code a guest must not see. Console shows a secret as Set or Not set, with a field to replace it and Clear. A secret setting takes no default: one with a non-empty default, or a value from a deploy entry's settings, is refused, and reusable quest types refuse secret settings. A secret's entry in a release's fields says secret: true. Settings without secret behave as before. Withholding follows each release's own fields, so releases and deployments from before a setting became secret still show its old value: set a new one after marking an existing setting secret.

  • 8ee88a9: Test members can redeem an offer: management.testMembers.redeem(member, { offer }), with the offer's key or ID, and domino test-members redeem MEMBER OFFER. The redemption runs as a signed-in member's would, with the same price, stock, per-member limit, requirements and refusals, writes the same redemption and entitlement, answers the redemption, and replays by action ID. It works in the test environment only, and the audit names the operator. A test member with a redemption can't be reset.

  • 6fe6f22: The testing kit lists and replays every capability reader, and a few small defaults change.

    • capabilityCalls. runQuest, createTestContext and replayAttempt results gain capabilityCalls: every capability call as { sequence, request, outcome }, with an open CapabilityCallRequest whose kind names the reader. It includes the calls calls left out: Telegram, NFT, contract call, delegation, wallet event and transaction reads, and the member's state. calls is deprecated.
    • Replay divergences of any reader. replayAttempt reports a call of any reader the capture cannot answer in the new capabilityDivergence, and stops there unless facts answers it. Before, such a call of a newer reader failed the replay with TestSetupError; now the replay resolves with decision: null. divergence is deprecated, and is null when the first divergence is a newer reader's call.
    • Tolerant replay. A captured answer with a field or an enum value a later Relay added, such as a new field on a Discord message, now replays instead of failing. Answers in facts are still checked strictly.
    • Reader fakes. fakeWalletReader(facts) and fakeTelegramReader(facts) from @domino-sdk/relay/testing build readers with every method Relay supplies, for tests that assemble their own evaluation context. The readers added after 1.0 stay optional in their types; quest code calls them with a non-null assertion, as the snippets now do (ctx.wallet.nfts!(…), ctx.telegram!.connection!()), instead of guarding against a runtime that lacks them.
    • Behavior change: Telegram trackers fire once per record. automatic() on a telegram.reactions() or telegram.messages() tracker now defaults to on: "new-record" instead of "new-observation", so a reaction removed and added again no longer runs the quest again. Published quests keep the mode they were published with; republishing one with this SDK switches it unless it sets on.
    • domino check warns TRIGGER_REFIRES_ON_REFRESH for a non-content quest on a YouTube Shorts or TikTok tracker that runs on every observation: each refresh evaluates it again for every video. Use content({ tracker }) with performance for a metric threshold.
    • RelayProvider takes followWallet. followWallet={false} keeps a wallet sign-in's session when the wallet switches account or locks. The default, true, signs out as before.
    • Behavior change: unauthenticated ends the session. A participant backend's (createParticipantBackend) 401 unauthenticated now ends the member's session in the SDK's experience, progress and activity observers, as not-signed-in does, instead of reporting an error. RelayErrorCode.Unauthenticated names it.
    • enrollment on not-enrolled skips. Tracker trigger outcomes, relay.quests.outcomes() and automatic outcomes carry an optional enrollment on not-enrolled skips: "quest" for the quest's own enrollment, "campaign" for its campaign's. A campaign skip on a tracker trigger now says "Member has not enrolled in the quest's campaign".
    • Naming. A leaderboard's balance is the ledger it ranks and its points pool's ledger the one the pool pays into; both are documented on the type. New fields that name a ledger are called ledger; existing balance fields keep their name in 1.x.
  • 561f4ab: Tiered rewards (defineTieredReward) can be listed, archived and restored. management.resources.list() and domino resources list list them in a new tieredRewards array (managedTieredRewardSchema), beside offers, and domino tiered-rewards list lists them alone. The experimental management.tieredRewards.archive and unarchive (POST /management/v1/tiered-rewards/REWARD/archive|unarchive) and domino tiered-rewards archive|unarchive KEY --reason archive and restore one in test, by key or ID, with publish permission; a promotion carries it to live, and Console Settings → Resources lists them with the same controls. Each returns { resource, changed }, typed by tieredRewardLifecycleResultSchema. While one is archived, a deploy or promotion that would make a quest, offer or leaderboard prize newly grant it fails with 422 tiered-reward-archived, naming it in reward. What already granted it keeps granting it, its entitlements and handover keep working, and its key stays taken. Console Rewards leaves archived tiered rewards out of its rewards. Promotions now list tiered rewards as definitions of kind tiered-reward, and a rollback archives one the earlier promotion did not carry.

  • 8d79fd6: Four operations that had two public paths with different results now agree, or say how they differ:

    • management.deployments.promote, submit and reject are deprecated, removed in 2.0.0. promote prepares a promotion of all of test and publishes nothing; its JSDoc now says so, and that the answered id is the promotion's, read with promotions.get and rejected by deployments.get and publish. Use management.promotions.prepare() with a client for test, and promotions.reject to decline. submit and reject act only on live deployments made before live refused them. domino deploy --promote is deprecated in favor of domino promote --environment test, and prints a warning naming it.
    • management.testMembers.acceptAnyway(member, { attempt, rules: "live", reason }) applies live attempts.acceptAnyway's rules in test: reason is required, and an archived quest, a superseded attempt, a completion cooldown, an exhausted budget, an existing or revoked completion and a used key are refused with live's 409 codes, leaving the attempt and balances unchanged. The default (rules: "test") still accepts those, with no reason, so a tester can get a test member unstuck. A reason is recorded with the decision either way. domino test-members accept-anyway MEMBER ATTEMPT --live-rules --reason TEXT sends it.
    • GET /v1/reward-deliveries and GET /management/v1/reward-deliveries answer items beside deliveries, with the same rows, so paginate((cursor) => relay.me.rewardDeliveries({ cursor })) and management.rewardDeliveries({ cursor }) read every page. Both clients fill items from deliveries when an older Relay sends only deliveries. useExperience().deliveries reads the role deliveries again on each delivery.updated activity event, so a role an operator revoked shows without waiting for focus.
    • defineLeaderboard prints a warning, and domino check reports a LEADERBOARD_TIES_DIFFER warning, when a board's live ties differs from its settle.ties, including a board that sets only settle.ties: "earliest", whose standings share ranks while its prizes pay first-to-reach. leaderboardTiesWarning(board) from @domino-sdk/relay/authoring returns that message, or null. domino check's diagnostics now include warnings from data definitions beside its errors; only errors fail it.

Patch Changes

  • a7ede97: The Domino skill references link to the new access page, which lists every kind of credential, the roles and permissions behind it, and what each may do in live, instead of restating the CLI's live limits in the error codes reference. The analytics, on-chain actions and campaign-testing references link to their matching guides. No command or behavior changes.

  • d5fe3db: domino dev prints "Hosted test quests are up to date." when a quest update succeeds after a failed one without publishing anything new, for example after reverting the change that broke the build. Console's Playground reads it to clear the build error its Map tab shows.

  • 1fa78d2: limits reads Relay's platform limits as number, so a 1.x release can raise one or add one (released SDKs keep checking the values they shipped with, so Relay answers them within those): limits.quests, limits.questTypes, limits.collections, limits.sdkRuntime, limits.memberCompletions, limits.dashboardPanels, limits.dashboards, limits.defer, limits.retryCooldown, limits.analyticsQueryRows, limits.apps (secrets, KV and SQL stores, migrations, storage browsing and schedules), limits.stagedDeployment, limits.capabilityCalls, limits.trackerData, limits.trackerPreview, limits.walletsPerChain, limits.walletEventLogs and limits.walletTransactions. It is exported from @domino-sdk/relay/schema, /authoring, /server and /testing, with its Limits type.

    Deprecated, with unchanged values and types: the literal-typed limit constants limits replaces, which can never change in 1.x. They are questScaleLimits, sdkRuntimeLimits, maxMemberCompletions, dashboardLimits, deferPolicy, retryCooldownPolicy, analyticsQueryLimit, APP_SECRET_LIMIT, APP_KV_LIMIT, APP_SQL_LIMIT, APP_MIGRATION_LIMIT, APP_MIGRATION_FILE_BYTES, APP_MIGRATION_TOTAL_BYTES, APP_STORAGE_ROW_LIMIT, APP_STORAGE_PREVIEW_CHARACTERS, APP_STORAGE_CELL_CHARACTERS, stagedDeploymentLimits, APP_SCHEDULE_LIMIT, APP_SCHEDULE_MIN_INTERVAL, APP_SCHEDULE_TIMEOUT, APP_SCHEDULE_RESULT_BYTES, maxCapabilityCalls, trackerDataMaxBranches, trackerDataMaxEntries, trackerPreviewLimits, WALLETS_PER_CHAIN, maxWalletEventLogs and maxWalletTransactions.

    Deprecated and removed in 2.0.0: Relay's own implementation helpers, which apps and quests have no use for. From @domino-sdk/relay/server: participantRoutes, participantRoutePattern, sequenceCursorSchema, sourceDataSchema, trackerDataIssue, trackerDataIssues, whereSchemaIssue, validateQuestShape, containsReferralIntegrationReward, REFERRAL_INTEGRATION_REWARD_ERROR and nextScheduleRun. From @domino-sdk/relay/schema: dashboardIssue, normalizeDashboard, compatibleSettingKinds, isBoundSetting, whereSettings and sourceContent. They keep working until then.

    z from @domino-sdk/relay/authoring stays zod 4 for all of 1.x.

  • 4842e30: Wording only. The app identity kind example is now app:game, including the validation message "Name an app identity kind like app:game: …", since a Telegram account is vouched for rather than modelled as an app identity. The JSDoc of progress(), quests.claim, me.claim and the quest interactions says which meaning of "progress" and "claim" each uses, and domino test-members pass help calls progress(...) quests counter quests. Examples use the @XDevelopers X account. The changelogs mark the versions that were never published to npm.

  • 425e4df: Internal: the "HTTPS, or plain HTTP on a loopback host" check for API URLs, sign-in URLs and portal proxy origins now has one implementation, shared through @domino-sdk/relay/internal. Published behavior is unchanged.

  • 41f63cb: More CLI commands now call the SDK management client instead of building management routes by hand: groups, domains, webhooks, schedules, secrets, storage, stock-items, leaderboard-settlements, members identities and identity-links, quests enrollments, quests replay, and every read of the signed-in credential's access. Their responses are checked against the SDK's schemas, and a malformed answer fails with INVALID_RESPONSE instead of being printed. --json output is unchanged.

    A few things behave differently:

    • domino groups list and domino groups members refuse a --limit outside 1–100 with INVALID_ARGUMENT (exit 2), as the other paged commands do. Before, any whole number was sent to the API.
    • An ID, cursor or body that the SDK rejects now fails with INVALID_ARGUMENT (exit 2) before anything is sent, where the API used to answer 400 or 404.
    • domino webhooks replay, pause and resume send a generated action ID when you don't pass --action-id, so a retry after a transient failure is applied once.
    • Paged reads always send the route's default limit.
  • e71245d: createParticipantBackend accepts onExchangeError(error, request), called with each refused sign-in exchange as Domino answered it (a ParticipantExchangeError with status, code, Domino's message and retryAfter) before the browser gets its answer, so the app's server can log why sign-in failed. The browser's answer is unchanged, also when the callback throws.

    Behavior change: creating an app login key for one of Domino's own app addresses in the environment that address does not serve is now refused with 400 invalid-request, and the message names the right environment. A <project-slug>--staging.domino.page or preview address belongs in Test, a live <project-slug>.domino.page address in Live. Other origins, custom domains included, can still use either environment, and existing keys keep working. An origin that is a URL but not an exact HTTPS origin is refused with a message that names the origin it accepts ("Use the exact origin https://app.example.com, without a path, query or trailing slash.") or "Use an HTTPS origin." instead of "Use an exact HTTPS application origin"; over HTTP the management API now answers that message instead of "Invalid management request". The app login guide and the Domino skill references gain a troubleshooting section with the exchange refusal codes.

  • ff2e96d: The CLI skill references and the RELAY.md that domino init writes now show commands that run as written: the wallet-balance tracker example passes its key, title and start (domino trackers create KEY --title T --source wallet-balances --chain C [--token T] --start MS), the stocked-offer test reads redemptions with domino redemptions history instead of the deprecated domino redemptions list, RELAY.md says domino quests list and domino releases list instead of the deprecated bare domino quests and domino releases, and the authoring reference lists every warning domino check reports (LEDGER_REFERENCE_DYNAMIC, LEDGER_DECLARATION_MISSING, AUTHORING_IDENTITY_DYNAMIC and LEADERBOARD_TIES_DIFFER were missing).

  • 8278803: Relay now refuses a malformed personal API key ID with 400 invalid-request ("Use the key's ID, such as ak_…") on DELETE /management/v1/tokens/:id and PUT /management/v1/tokens/:id/scope. A key ID is ak_ followed by letters, digits or _. IDs from tokens.list() and domino tokens list already have that form, so no working call changes: an unknown ak_… ID still revokes as a silent success, and a key that belongs to someone else is still refused with 403.

  • eb8572b: The testing kit's participant API answers an activity cursor it cannot read as Relay does: 400 invalid-cursor, "Invalid cursor. Start from the first page.", instead of invalid-request.

  • Updated dependencies [cc04dae]

  • Updated dependencies [1fa78d2]

  • Updated dependencies [4842e30]

  • Updated dependencies [425e4df]

  • Updated dependencies [8515f3f]

  • Updated dependencies [052744c]

  • Updated dependencies [e71245d]

  • Updated dependencies [eaaae2a]

  • Updated dependencies [69e017d]

  • Updated dependencies [bbc3623]

  • Updated dependencies [8278803]

  • Updated dependencies [9cb68e2]

  • Updated dependencies [8ee88a9]

  • Updated dependencies [6fe6f22]

  • Updated dependencies [eb8572b]

  • Updated dependencies [561f4ab]

  • Updated dependencies [8d79fd6]

    • @domino-sdk/relay@1.63.0

1.62.0

Minor Changes

  • a341b16: Analytics counts Relay's own key-used rejection, of an attempt whose completion key another member's completion holds, as its own rejection source. rejectionsBySource gains keyUsed, and analytics.query's source breakdown a keyUsed row; evaluator no longer includes them, while an evaluator's own key-used still counts there. Each topReasons item carries codeSource (relay, evaluator, or null without a code), and Relay's key-used and an evaluator's are separate rows, in topReasons and in the reason breakdown. Both fields are optional in the schema, since older servers don't send them. domino analytics get marks the code of a top reason Relay wrote, such as [relay key-used] or [relay budget-exhausted].

  • 5233be5: The experimental billing results read the plan as BillingPlan (KnownBillingPlan | (string & {})): subscription.plan and scheduledChange.plan in management.billing.read(), cancel(), resume() and organization(), billingSubscriptionSchema, projectBillingSchema and organizationBillingSchema, and the --json results of domino billing show, billing organization, billing cancel and billing resume, so a plan Relay adds no longer fails to parse. Handle a plan name this SDK does not know. The stable management.usage.plan() and domino usage plan are unchanged: the subscription they carry keeps the plans this SDK knows (launch, growth, scale).

  • 3665a4e: Billing is experimental while it is open to Domino staff only: the management.billing methods, the billing schemas and types in @domino-sdk/relay/server, and the domino billing commands may change in a minor release, plan names included. domino billing now appears under Experimental commands. management.billing.checkout() and changePlan() take any plan name (BillingPlan, KnownBillingPlan | (string & {})), and domino billing checkout and billing change take any --plan; Relay refuses a plan it does not offer with 400, as before. management.usage.plan() and domino usage plan stay stable.

    The CLI's JSON result schemas now leave objects open, as documented, so a JSON Schema validator such as Ajv accepts fields a later release adds; only the variants of a oneOf stay closed. webhooks deliveries, storage rows and storage keys results declare the optional nextCursor the API already sent. secrets, storage and schedules results include environment, and these commands warn on stderr when RELAY_ENVIRONMENT or relay.json names live while they use test. contract.json marks deprecated options with deprecated, as it does commands, and capabilities.json no longer lists deprecated commands. management.referrals.control and referralControlSchema now point to pause and resume instead of the deprecated suspend.

  • f03828d: domino deploy --only <quest-key> (repeatable) deploys just the named quests, with the SDK runtimes they import, the reference images their code names and supportedInteractions. Every other quest keeps its current release and every other resource stays as published, so parallel worktrees can publish their own quests without reverting each other's. It works with --preview and --yes; an unknown key, or --only with --resume, --promote, --dry-run or --out, fails with INVALID_ARGUMENT before any request.

  • 361a5b1: A deployment keeps the active release of a quest it doesn't change, instead of publishing an equal one with a new ID. Deployment previews and results carry questChanges: each quest by key, created, updated (with the release it replaces) or unchanged. domino deploy lists the created and updated quests in its confirmation and in the --preview --json summary, so a deploy that would replace a quest you didn't touch shows before it publishes.

  • 68efdac: domino stage warns when the project has leaderboards (declared in relay.json or published in test) but its test profile settings are private or name no default account, so every leaderboard row shows a generated name such as Participant 1a2b3c4d. The warning goes to stderr and, as generated-leaderboard-names with the domino profiles configure command that shows members' names, into the JSON result's warnings; the build still stages. domino doctor --remote reports the same as its leaderboard-names check. The Domino skill now says where leaderboard names come from and to ask the owner before showing them.

  • 90f9465: domino integrations discord get reads the project's Discord connection, matching the get name other single reads use. domino integrations discord status still works, prints a deprecation warning outside --json, and is removed in 2.0.0. domino collections list lists the project's collection slots in the environment, archived ones included, as { collections: [{ id, key, title, archivedAt }] }.

  • 98ad384: A member import carries its real state in a new state field: open, checking, checked, applying, applied, stopped, reverting, reverted or canceled, typed open (KnownMemberImportState | (string & {}), with MemberImportState listing the known values), so Relay may add states. status is unchanged and still reads applied for a stopped, reverting or reverted import, so code filtering on status === "applied" counts those too; read state instead. Import rows gain reverted, true once the import's revert went through the row. Both fields are optional in the schema, since older servers don't send them. domino import and domino imports results include state, and the CLI polls on it.

    MemberSummary (relay.me(), useMe()) now types email, emails and clock as required, since Relay always sends them: code no longer needs to check them for undefined. The SDK reads a /v1/me response from a Relay older than these fields with email: null, emails: [] and a clock at real time (offset 0), the same as code treating them as absent did. memberTime() still accepts an object without clock.

    MemberSummary, LeaderboardPage, LeaderboardHistoryPage, Badge, MemberBadge and MemberBadges are marked as response types Relay may add properties to.

  • 4bf82e5: CLI results and options follow the management API conventions more closely:

    • Every command in contract.json has a recovery: resume, replay, reconcile, or null for a command that changes nothing in the API. It says what to do when the command exits 3 with OUTCOME_UNCERTAIN; recoveries in contract.json describes each value. Every OUTCOME_UNCERTAIN error that names no saved operation now carries details.recovery: "reconcile", as the automation reference promised; before, only domino api and tokens create set it.
    • offers pause|resume, quests pause|resume, redemptions pause|resume, redemptions cancel and entitlements restore take --expected-revision N. --revision N is its deprecated name, removed in 2.0.0, and keeps working; passing both with different numbers exits 2 with INVALID_ARGUMENT.
    • webhooks replay takes --action-id <uuid>. Relay keeps a receipt for it, so a retry with the same ID answers the delivery the first replay queued instead of queuing another, and the CLI sends it again after a lost answer.
    • tokens list --json answers the keys in items as well as tokens.
    • test-members advance --to also takes epoch milliseconds.
  • ea2c9be: A retention job can delete the evidence of a quest's attempts decided before a cutoff: management.attempts.redactEvidenceBefore({ quest, decidedBefore, reason }) (review permission; in live it also requires an organization administrator's Console session or personal key, or a service key, and refuses any other caller with 403 permission-required, because the deletion can't be undone) deletes up to 100 attempts' evidence per call, oldest decision first, exactly as attempts.redactEvidence does for one, and answers more: true while others remain. Attempts still waiting for a decision are skipped, and a decidedBefore in the future is refused with 400 invalid-request. Each call records one evidence-redacted-before audit event. domino attempts redact-evidence-before --quest QUEST --decided-before MS --reason TEXT repeats the call until nothing is left and prints the total.

  • e58c2ae: Name a member's account the way a member import does, everywhere an operator command takes one: { kind: "x" | "discord" | "telegram", subject } with the numeric user ID, { kind: "app:<name>", subject }, or { kind: "wallet", chain, address } (identityReferenceSchema, IdentityReference).

    • members.identityStates.unlink and relink take it as identity (memberIdentityStateChangeInputSchema, MemberIdentityStateChangeInput); setSignIn also takes a Cosmos wallet with its network. An X or Discord subject names the member's selected account with that subject, else the only one they hold. A subject the member holds through several issuers, none of them selected, fails with 409 identity-ambiguous; name it by issuer then.
    • members.lookup takes it, and sends it as the provider and subject every Relay version reads.
    • testMembers.linkAccount and unlinkAccount take { actionId, kind, subject } for an X or Discord account.
    • The CLI takes --kind and --subject on domino members lookup (with --chain and --address for a wallet) and on domino test-members link-account and unlink-account.

    Deprecated, removed in 2.0.0: the { kind: "oauth", provider, issuer, subject } and { kind: "app", appKind, subject } input selectors, memberIdentityLinkChangeInputSchema and MemberIdentityLinkChangeInput, members.lookup({ provider, subject }), the test-member { provider, id }, and the CLI's members lookup --provider and test-members link-account|unlink-account --provider|--id. They keep working through 1.x; contract.json marks the deprecated options with deprecated.

    Identity reads gain an optional source: member, import, app-login or test-link, an open string (IdentitySource, KnownIdentitySource), and import, the ID of the member import that attached the identity when Relay recorded it. Identity states of every kind carry them, and so do members.get's wallets and identities and the deprecated oauthAccounts and telegramAccount. imported is unchanged.

  • 3a3feef: A leaderboard can rank its live standings first-to-reach. defineLeaderboard({ ..., ties: "earliest" }) orders equal scores by each member's latest qualifying entry, earliest first, then by member ID, and gives every member who scored their own rank: the order a settlement with settle.ties: "earliest" freezes. Standings pages, me, nearby, placement, and the management and CLI standings reads all follow it, so a tie-broken board is one paged read.

    ties is optional. Omitted or "shared", equal scores share a rank as before, so published boards rank unchanged. It is independent of settle.ties; set both on a board that settles so members see the order prizes follow. Members listed through includeUnscored still share one rank after every member who scored.

  • 10826dc: An organization administrator's CLI session lists the project's identity integrations (app login keys) and their claimed kinds: domino identity-integrations list and domino identity-integrations kinds, or management.identityIntegrations.list() and kinds(). They return each key's origin, identity kind and secrets' expiry and last use, never a secret. Creating, rotating and revoking keys and their secrets stays in Console; non-admin CLI sessions and every key type are refused.

  • 80e09c2: Experimental: operators read a member's marketing consent. management.members.consent(member) lists the member's current consent, one entry per channel they chose on, and management.members.consentHistory(member, { cursor, limit }) pages through every change on every channel, newest first, as { items, nextCursor }; each entry is { member, channel, state, at, wording, source }, and memberConsentHistorySchema and memberConsentHistoryQuerySchema are exported from @domino-sdk/relay/schema. Both need read permission (GET /management/v1/members/:member/consent and /consent/history); a member who never chose reads as empty, and an unknown member fails with 404 not-found. domino members consent MEMBER prints both, with --limit and --cursor for the history. The Console member page shows the same, read-only. There is no management write: only the member grants or withdraws consent. The consent API is @experimental and can change in a minor release.

  • 1c61073: domino tokens list|create|scope|revoke manage your personal API keys from a domino login session, as management.tokens does. domino tokens create --name NAME --permission P [--ingest-tracker T] [--days N] issues a key for the selected project and environment and prints its one-time token and expiry; --json prints the SDK's answer, so domino tokens create … --json | jq -r .token | domino secrets set NAME stores it. A personal key expires after days, 1 to 90, default 30; --days 91 is refused before anything is sent. domino tokens list shows each key's expiry. API keys still cannot manage keys (credential-not-allowed), and a credential that must not expire is a service key, which an organization admin creates in Console.

  • 423b6fc: Plugin connections, experimental. management.connections (list, get, connect, update, disconnect) connects a project to the plugins Domino writes, separately in test and live; a promotion never copies a connection. A connection's config is checked against the plugin's JSON Schema (422 invalid-config names the failing path), its secrets are write-only and sealed at rest, every change takes an actionId, and update and disconnect take an expectedRevision (409 connection-changed). The new exports (connectionSchema, connectionPageSchema, Connection, ConnectionStatus and the connect, update and disconnect inputs) are experimental and may change in a minor release. The CLI adds the experimental domino connections list|get|connect|update|disconnect, test unless --environment live, reading secrets from stdin as JSON or at hidden prompts, never from arguments.

  • 2a83792: Plugin OAuth connections, experimental. A plugin whose plugins.list() entry has oauth connects through its vendor's OAuth instead of entered secrets: management.connections.authorize(plugin, { config, returnTo?, actionId? }) answers { authorizationUrl, expiresAt }, a person opens the URL and grants access, and the vendor's callback makes the connection ready. The URL works once, for ten minutes. Domino refreshes the tokens before they expire and never answers them; when the vendor refuses a refresh, the connection is attention with the new reconnectReason (revoked or expired), and authorizing again reconnects it. Disconnecting deletes the tokens, then revokes them at the vendor; a failed revocation is logged. connect refuses an OAuth plugin with 409 oauth-required, authorize refuses one connected with secrets with 409 oauth-not-supported, and a deployment without the vendor's OAuth app with 409 plugin-not-configured (oauth.configured is false). New experimental exports: connectionAuthorizeInputSchema, connectionAuthorizationSchema, ConnectionAuthorizeInput, ConnectionAuthorization, ConnectionReconnectReason, KnownConnectionReconnectReason and knownConnectionReconnectReasons. The CLI adds the experimental domino connections authorize <plugin> --config-json …, which prints the authorization URL.

  • d64625c: domino quests list --lifecycle <state> prints only the quests in that lifecycle state: active, paused or archived. Repeat the flag for more than one state. Any other value exits 2 with INVALID_ARGUMENT before a request is sent. Without the flag, the list is unchanged; each quest in it carries lifecycle.

  • b2b8127: A full deployment to test warns quests-not-in-bundle with the keys of quests published from code that it leaves out, such as a starter's sample quests whose files were deleted: they stay as published until archived with domino quests archive <key> --reason "…". Archived quests and quests created in Console are not listed. A deployment input with partial: true doesn't warn, and domino deploy --only sends it. domino stage now prints the warnings of the deployment its build published, and its --json result carries them in warnings; a build's status (GET /builds/{id}) names that deployment in deployment.

  • a381dde: Staff can delete the evidence a member submitted for a decided attempt: management.attempts.redactEvidence({ attempt, reason }) and domino attempts redact-evidence ATTEMPT --reason TEXT (review permission, test and live). The photo, text, URL or quiz answers, and what the quest's capabilities read from them, are deleted; the attempt keeps its outcome, completion and rewards. Attempts that submitted the same photo are redacted together. management.attempts.get answers the new redaction ({ at, actor, reason }). Undecided attempts are refused with attempt-not-decided, and attempts.retry refuses a redacted attempt with evidence-redacted.

  • c8452bc: Add domino starters export <template> --directory <dir>, which writes the starter that domino create uses today, including .domino/starter.json, into a new or empty directory so an existing hosted project can move to a newer starter.

  • 7fa2a06: Test members can submit photo quests. management.testMembers.uploadEvidence(member, file, { actionId }) uploads a JPEG or PNG of at most 5 MB as the test member's photo, and act submits its id as evidence. domino test-members act MEMBER QUEST --photo FILE does both, so the quest's real image check and the review queue can be proven from the CLI. The campaign-testing reference now points photo evaluator tests at facts.images.review.

  • 4391bad: Every domino test-members change takes --action-id ID: any action ID the management client accepts, sent as the change's actionId without saving an operation, so the CLI and the management client can retry the same action.

Patch Changes

  • 8439ff6: The Domino skill references and capabilities.json now match shipped behavior. Console shows dashboards on Analytics. Webhooks list all nine event types. A new quest-announcements capability covers quest completion announcements. The trackers reference lists pause, resume, refresh and triggerOutcomes, says the plan's minimum refresh interval applies to every collected source, and explains that a spent quest budget rejects the attempt instead of skipping the observation. Deprecated methods are no longer recommended: use useRedeemOffer().mutate and members.identityStates.relink.

  • ee3f488: Bulk management writes (catalog.deploy, catalog previews and drafts, deployments.preview, releases.publishBatch, tracker record ingest and memberImports.addRows) refuse a body with more than 2,000,000 JSON values with 413 payload-too-large before parsing it, whatever its byte size; other management writes refuse one past 500,000. A server handles one body over 4 MiB or 500,000 values at a time and answers another 503 with Retry-After when it has waited 10 seconds. memberImports.addRows now takes at most 64,000,000 bytes, like the other bulk writes, so a single batch above 64 MB from an older CLI gets 413. domino import now splits rows into batches of at most 500 rows, 16 MB and 1,000,000 JSON values, sends a row larger than that in a batch of its own, and refuses a row too large for one call (63 MB or 1,990,000 values) before uploading anything, naming its line.

    The management client, the CLI and the participant client now send any call again after a 503 with Retry-After, within its deadline and attempts, including writes without an action ID: Relay answers such a 503 only to a request it refused before doing anything. Before, those writes were sent once.

  • f13cfb6: A domino login CLI session creates and scopes personal API keys in test only. management.tokens.create with environment: "live", and management.tokens.scope on a live key, now answer 403 console-approval-required from a CLI session, whatever X-Relay-Environment selects: a live key would carry the live operations (points, attempt confirmations, reviews, pauses) that the CLI session itself is refused, and would outlive domino logout. Create and change live API keys in Console (Settings → API keys). A CLI session still lists its keys and revokes any of them, live included, and Console sessions are unchanged.

    This changes a 1.x behaviour, an exception Peter Szoke granted on 2026-10-15: no request or response shape changes, and the refusal is a typed 403 that says where to go. domino tokens create --environment live prints that refusal, and domino tokens --help says live keys come from Console.

  • c391c86: domino deploy --promote <id> --environment live runs in the checkout's scope, from relay.json like domino promote, instead of failing with SCOPE_REQUIRED. It starts a new operation rather than resuming one; a promotion whose response was lost is still recovered with domino operations resume <id>, in the scope it journaled. domino deploy --resume and domino stage --request-id still require every scope key as a flag or RELAY_* variable.

  • 154af63: Discord message and reaction records name a thread's parent channel: DiscordMessageData and DiscordReactionData gain parentChannelId, the channel a thread belongs to (a forum post's forum), or null outside a thread. It is absent when it is unknown, because Discord's channel read failed. channelId stays the thread's own ID. A trigger's where: { path: "parentChannelId", equals } selects a channel's threads; to count a channel and its threads, compare both fields in the evaluator. management.testMembers.record and domino test-members record --data take parentChannelId for Discord records.

  • 550b610: domino doctor --remote's participant-api check, its scope.participantApi, and domino dev read the apiUrl written in relay.json, not the management URL a checkout scope (domino checkout --api-url) keeps in the Git directory. A checkout made with --api-url https://console.domino.run/relay and a relay.json pointing at https://relay.domino.run is now healthy instead of reporting relay.json as wrong.

  • b83512e: domino deploy reports a timeout or lost connection while preparing its preview as TIMEOUT or NETWORK_ERROR (exit 1) and says nothing was published, instead of OUTCOME_UNCERTAIN with no operation to resume.

  • 5e635dc: The CLI requires Node 24.5.0 or later (engines.node is >=24.5.0 <25), because its first request loads system certificate authorities with tls.setDefaultCACertificates, which Node 24.5.0 added. On an earlier Node 24 release domino login and every other command now exit 1 with Domino CLI needs Node 24.5.0 or newer instead of failing the first request with Could not reach the API: setDefaultCACertificates is not a function. The NODE_VERSION_UNSUPPORTED error adds details.minimumVersion (such as "24.5.0"); details.minimum stays the major version.

  • 986f989: domino login says when its code expires, and the --json browser-login event carries expiresAt (epoch milliseconds). A sign-in request now lasts 60 minutes instead of 10, so the person approving it can come back to the link later.

  • 9ebd5dc: domino create waits for a slow repository setup instead of stopping at the 30-second request deadline. When a step times out, loses its connection, or gets a 502, 503 or 504, it resumes the same saved operation for up to 5 minutes and then prints the normal result. Without --json it says once on stderr that it is still setting up the project. Past 5 minutes it fails as before, with the operation to resume and a message in plain words: Domino may still be setting up the project, or the API stayed unavailable.

  • f204520: domino stage ends its human output with who can open the staging URL: staging is private and runs on test data, reviewers outside Console open it with a staging password (Console Settings → Staging access, or domino api PUT /staging/access), and members see the app only once a promotion makes it live. --json output is unchanged. The Domino skill's hosted reference shows the PUT /staging/access body and tells agents to ask the owner before setting or changing the password.

  • 44a083c: The Discord capability tells agents that a Console administrator without Manage Server can send a one-time install link (Send to your server admin, valid seven days) to whoever manages the server, who needs no Domino account.

  • acad848: domino doctor outside a project reports setup health only: no relay.json is a manifest warning instead of an error, the skill loader check and the agents field are left out, and a machine with nothing else wrong reports healthy: true and exits 0. Inside a project nothing changes. Human output labels the loader field "Skill loaders", since it lists the Domino skill loaders installed in the project, not which agent is running doctor.

  • 8b95b72: The Domino skill's trackers reference now covers declaring trackers: maintained X datasets, rewarding X posts, tracker triggers, content admission and performance gates, daily engagement points, and the Discord, Telegram, YouTube Shorts, TikTok and wallet balance sources. They moved there from the authoring reference, which links to them, so domino reference trackers serves them and an agent writing a quest no longer loads them.

  • 2112bd9: The Domino skill has a new verification reference for checking what a member did: the evaluator reader rules (null results, errors and pinning), declared integrations, Discord membership, X, linked wallets, wallet balances, NFT holdings, contract state, Cosmos delegations, on-chain actions, text and URL submissions, AI text review, image checks, staff confirmation, and entry after app-owned pass verification. These sections moved there unchanged from the authoring reference, which links to them, so domino reference verification serves them and an agent writing a quest loads them only when it needs them.

  • a06d381: The Domino skill has a new quest-rules reference for how a quest decides and repeats: campaigns, windows and prerequisites, automatic and repeatable quests and republishing them, prerequisite automatic outcomes and test members, reject codes, deferred evaluation and progress reports, distinct-action progress counters, retry and completion cooldowns, evaluation time and randomness, calendar-day streaks, and testing quests locally. These sections moved there unchanged from the authoring reference, which links to them, so domino reference quest-rules serves them and an agent loads them only when it shapes quest behavior.

  • d820a34: The Domino skill has a new management reference for operating published resources: the management client and its error contract, project identity and settings, resource lifecycle, canonical lifecycle commands, recovering a quest lifecycle change and its keys, operator undo commands, and scheduled hosted jobs. These sections moved there unchanged from the authoring reference, which now covers writing and publishing quests, rewards and presentation and links to the trackers, verification, quest rules and management references, so domino reference management serves them and an agent loads them only when it operates a project.

  • 56a0601: fakeEvaluationMember(methods) from @domino-sdk/relay/testing builds an EvaluationMember for a test that assembles its own evaluation context. Pass the methods your code reads; the rest answer for a member with no linked accounts, completions, groups or enrollments, and balance and referrals fail with a CapabilityError of code author-error. A member written by hand stops compiling when a minor release adds a method; one from fakeEvaluationMember does not.

    The CLI's automation contract (cli/contract/contract.json) lists the JSON Schemas of relay.json and the checkout's domino.json under files, so a later release is checked to accept every file an older CLI wrote.

  • 5cc0d4d: Revoking a completion with freeOccurrence: true now returns the budget it used. Each quest budget the completion was charged gets that charge back, in the same transaction and in the budget period it was charged to, so the freed occurrence can be completed again within the cap. A point-priced budget gets back its charge less the points the revoke couldn't reverse. CompletionRevocation reports this in a new optional budgets (budget, released). Revoking without freeing the occurrence leaves budgets spent, as before. Completions recorded before this release give no budget back and report budgets: [].

    Restoring such a revocation charges that budget again and reports it in a new optional CompletionRestoration.budgets (budget, charged). The charge is checked against the budget's current limit, or the limit it was first charged under if the quest removed the budget or changed its period or cost. Restore now also refuses with 409 budget-exhausted, naming the budget, when a budget has no room left for the charge, and then changes nothing. This is a behaviour change: until now, budgets stayed spent after any revoke.

  • dcc32da: ctx.wallet.events() takes newest, from 1 to 1,000: it returns at most that many of the newest matching events, reading back from the newest block, and stops at the 3,000,000-block or 1,000-log cap instead of throwing author-error. WalletEvents gains an optional complete, false when a newest read left older matching events out or stopped before since. A busy wallet, or a long window on a fast chain such as Arbitrum One (3,000,000 blocks is about 9 days there), no longer has to fail the attempt. A call without newest behaves as before: it returns every matching event, now with complete: true, or throws author-error. A read whose start-block estimate overshot 3,000,000 blocks while the span itself fit is no longer refused. The onchain-action snippet reads with newest: maxWalletEventLogs.

  • 980e6ca: Sign-in rate limits count an IPv6 client by its /64, so every address in one /64 shares the per-IP limits on email, wallet, OAuth and Telegram sign-in starts, domino login device starts and staging password attempts. An IPv4-mapped IPv6 address counts as its IPv4 address. Per-address limits now count per client: three sign-in codes per email address and ten wallet challenges per wallet address from one IP address every ten minutes, so another client hitting them no longer refuses that address's own sign-in, on this project or another. Each project and environment also caps an address at ten sign-in codes and thirty wallet challenges every ten minutes from all clients together.

  • d683d00: Sign-in rate limits per IP address count each member of a Domino-hosted app on their own. Hosted apps reach the API from Cloudflare's shared Worker address, so until now every member of every hosted app shared one set of per-IP limits. Domino now attests the visitor's IP on every request a hosted app's server sends to the API, with no app change or rebuild. A request from Cloudflare's shared Worker address without Domino's attestation, such as from an app you run on your own Cloudflare Workers, is counted per project and environment instead of with every other Worker.

  • 081806c: Personal keys (ak_…) and MCP OAuth tokens that Clerk does not recognize now count against the client that presented them, on the management API, git and the MCP server: after 30 in 10 minutes from one IPv4 address or IPv6 /64, counted by each API server on its own, every ak_ and MCP token from it answers 429 rate-limited with Retry-After until the window ends. Expired, revoked and other refused credentials Clerk knows are not counted, and neither is Clerk refusing Domino's own secret key. A refused value presented again within a minute can be refused without asking Clerk, and a value no Clerk credential can be (under 16 characters after its prefix, over 4096, or with a character a bearer token cannot hold) is refused with 401 invalid-credential without asking Clerk. When Domino is checking many new credentials at once, a key or token it has not accepted recently can answer 503 with Retry-After: 1. Clerk's own rate limit now answers 503 with Clerk's Retry-After.

  • 114e0c7: Telegram and Discord install links now tell whoever opens them which Domino project they connect to. The Telegram bot's reply to a link, before the chat picker, names the organization, the project's name and slug, and the environment, and so does its Connected … reply. A Discord install link sent to a server's administrator (Send to your server admin) first opens a page naming the same, with a Continue to Discord link; the page changes nothing, so a link preview that fetches it stays harmless, and the page shown after connecting names the project too. Creating either link (management.telegram.connect(), POST /management/v1/integrations/discord/links) now reads the organization's name from Clerk, and answers 503 rather than issue a link that names nobody when Clerk can't answer. Links made before this change still work and name no project. Console's own Discord installation is unchanged.

  • 92ee725: Connecting a Discord server or Telegram chat where Domino's shared bot already holds rights now takes someone who holds those rights too. In Discord, installing the bot (Console's Connect Discord or a shared install link) refuses a user who is neither the server's owner nor an Administrator when the bot already has a permission there they lack, such as Manage Roles another organization gave it; the install page says "Domino's bot already has permissions in this server that you don't have. Ask a server administrator to connect it." and nothing is connected. A fresh install, where the bot holds nothing beyond @everyone, still needs only Manage Server. In Telegram, an administrator who lacks an administrator right the bot holds in the chat, such as posting messages, is refused with a reply naming the missing rights; the chat's creator always connects it. Reusing a Discord server the organization already connected is unchanged, and existing connections are not revisited.

  • d7df1d9: The automation skill reference documents management API rate limits: no general quota per key, project or organization, the calls that carry their own limits, and how to honor 429 or 503 Retry-After and pace a long run.

  • dd7ced3: Experimental: a member import row can carry the member's marketing consent from the platform they come from. The optional consent list on memberImportRowSchema takes one { channel, state: "granted" | "withdrawn", at, wording? } per channel; applying the row records it with its original time and wording, marked source: "imported", in the row's transaction, unless the member chose on that channel in Relay (their choice always stays) or an earlier import recorded consent as recent or more recent. A check refuses the row with future-action for a time in the future, unsupported-consent-channel for a channel other than email, consent-wording-required, consent-wording-too-long or consent-wording-invalid for the wording, and duplicate-entry for a channel named twice. KnownConsentSource gains imported. The CLI imports reference describes the field.

  • 5a34b36: Experimental: members record their marketing consent per channel. relay.consent.get() lists the signed-in member's current consent, one entry per channel they chose on (member, channel, state, at, wording, source), and relay.consent.set({ channel, granted, wording, actionId? }) grants or withdraws one channel. The useConsent() React hook returns the query with grant(channel, wording) and withdraw(channel, wording?). Relay stamps the time and stores the wording verbatim; a repeat of the current state and wording records nothing. Only email is accepted for now: other channels are refused with unsupported-consent-channel, a grant without wording with consent-wording-required, wording over 2,000 characters with consent-wording-too-long, and wording with a NUL or a lone surrogate with consent-wording-invalid. Channel, state and source are open strings in results. The consent API is @experimental and can change in a minor release. The participant API reference and capabilities.json describe it.

  • f463bdf: The campaign-testing skill reference explains how to pace a live test-member rehearsal: a driver process outside the hosted app, a deterministic actionId or --operation-id per step, bounded concurrency, and clock moves instead of real-time waits.

  • 172c3b5: Experimental: a viewer project role and grant end dates, to share a project's dashboards with a sponsor or client. A viewer holds one permission, dashboards, which reads dashboards.list and dashboards.read and nothing else: no members, evidence, quests or settings. Every other role holds it too, and read keeps reading dashboards, so no existing person or key loses access.

    management.grants.set takes projectGrantSchema: roles may name viewer, and an optional expiresAt (epoch ms, or null for no end) ends the grant's access without anyone removing it. An ended grant stays in grants.list(), and the access history records each end date. Released SDKs keep reading grants: a grant's roles keeps the roles 1.x names, so a viewer's is empty, and the new projectRoles lists every role. Likewise a project in management.access() keeps dashboards out of permissions and lists it in the new projectPermissions. projectRoleSchema, projectPermissionSchema, projectRolePermissions, projectGrantSchema and these fields are experimental.

    Console grants the viewer role and an end date under Settings → Access → Team, and shows an ended grant as Expired. A viewer's Console shows only Analytics, opened on the project's first dashboard. The CLI's agent references describe the role.

  • aaf28f9: Quest submissions are limited to 120 an hour per member in each project environment, counted together across POST /v1/me/attempts, POST /v1/attempts, POST /v1/me/submissions/:id/submit and POST /v1/attempts/:id/retry. Past it they answer 429 rate-limited with Retry-After, before the request is read. The quest controllers keep the saved draft, so resume() continues it. The participant reference in the Domino skill states the limit.

  • 9f39ea4: An operator's reversal of a referral payout stays reversed. Reversing all or part of a referral payout (Console point history Reverse, or management.points.record({ kind: "reverse", entry })) writes those points off on the referral account, so no later reconcile pays them again; earlier, the inviter's next invitee activity or a new acceptance of their code paid the reversed amount back. Later bonus growth still pays, and restoring a revoked qualification does not repay the reversed points. earned in the referral summary and the management referral accounts excludes the reversed points, so a reversed payout no longer reads as earned.

  • 10df298: When no credential exists for the selected API URL but domino login saved one for another, AUTH_REQUIRED names that URL and the fix (--api-url, a checkout that uses it, or domino login), and details.savedApiUrls lists the saved URLs so automation can retry. A saved token is still sent only to the API URL it was saved for. With no other saved sign-ins, the error is unchanged.

  • 1cdc64b: Staging a commit the project's hosted repository doesn't have (a mistyped SHA, or a commit not pushed yet) now answers a certain 422 commit-not-found before any build is reserved, instead of an uncertain 502 upstream-failed. domino stage reports it as REJECTED with apiCode: "commit-not-found", saves no operation to resume and prints no retry command: push the commit, then stage again. Other GitHub failures during the commit check still answer 502 and stay uncertain.

  • 3d2f654: Staging's participant API is now private, like staging itself. A participant request that names a staging address (<slug>--staging.domino.page) in X-Relay-App-Origin answers only when it carries the staging pass, which the staging gateway gives browsers signed in to staging; anything else is refused with 403 staging-access-required. Apps on staging keep working with no change: the gateway adds the pass to the requests it hands the app and to legacy /relay/* calls, and proxyParticipant forwards it with the browser's other cookies. Only callers that sent a staging origin straight to the API, outside the staging address, see the new refusal. The retired staging-<slug>.domino.run origin now selects nothing (403 "Application origin is not registered or is offline"). Previews, live apps and identity integrations are unchanged.

  • 2e8f383: The Domino skill's campaign testing reference explains the staging toolbar's bottom inset: while the toolbar is mounted, <html> carries data-domino-staging-toolbar and --domino-staging-toolbar-inset-bottom, so an app's fixed bottom bar can sit above the collapsed toolbar instead of under it. When Playwright reports "<domino-staging-toolbar> intercepts pointer events", the app should use the inset.

  • 75efef0: The skill's references list the participant reads that return a member's submitted text, URL or quiz input, and warn against text or URL quests for secrets. The trackers reference shows the pattern to use instead: the app's server checks the code, rate-limits guesses, and ingests a record for the member into a custom tracker that triggers the quest.

  • b9270ff: Telegram Login, the member's Telegram account and its unlink now go through the generic sign-in surfaces, and Telegram's own members for them are deprecated. They keep working, with their routes, through SDK 1.x and are removed in 2.0.0:

    • relay.auth.telegram.start(mode) → relay.auth.start({ provider: "telegram", mode }).
    • relay.auth.telegram.account() → the "telegram" entry of relay.auth.signInAccounts().
    • relay.auth.telegram.disconnect() → relay.auth.disconnect("telegram").
    • useTelegramAccount() → useAccount("telegram"). Inside a Mini App, link with relay.auth.telegram.miniApp.connect(initData), then call invalidate([relayKeys.accounts, relayKeys.experience]) from useInvalidate().
    • useSignIn().withTelegram() → useSignIn().withProvider("telegram"); withProvider now takes any SignInProvider.
    • SignInOptions.telegram and useSignInOptions().telegram, TelegramSignInOption and SignInActions.telegram → signInProviders, below.
    • relay.auth.providers().telegram.login and management.participantSignIn().telegram.login → the "telegram" entry of signInProviders. TelegramSignInMethods and the new ParticipantSignInTelegram are written-out types, so editors show these two deprecations.
    • management.members.telegramAccount(member) and unlinkTelegramAccount(member, input) → members.accounts(member) and members.unlinkAccount(member, { provider: "telegram", subject, expectedRevision, reason }).

    useSignInOptions() and signInOptions(relay) add signInProviders, the open sibling of the oauth entries of options: every sign-in provider Domino configured, Telegram included, in Relay's order, each a ProviderSignInOption ({ kind: "provider", id, label, icon, start() }). A newer Relay may list a provider this SDK does not know; its label is its name. Against a Relay without signInProviders, the list holds its providers and Telegram Login. SignInMethods adds signInProviders and SignInActions an optional provider(provider) action. The starters render their provider buttons from signInProviders.

    Mini App sign-in (relay.auth.telegram.miniApp, useSignIn().withTelegramMiniApp, providers().telegram.miniApp, participantSignIn().telegram.miniAppBotId) is not deprecated.

  • 90a20c6: Telegram is now a plugin: management.connections.list() and domino connections get telegram include it, ready while the environment has a connected chat, attention with the chat's issue while Domino's bot can't read one, and disconnected without chats. Chats still connect only through the bot (management.telegram.connect(), unchanged), so connections.connect, update and disconnect answer 409 plugin-managed for Telegram and for any plugin that connects through its own flow. knownPluginSlots adds announcement, the slot Telegram posts quest announcements through. management.plugins.list() items gain console: { component } when Console shows the plugin's own component instead of the generated card, otherwise null; the schema defaults it to null, since older servers don't send it.

  • 8e8e24a: Telegram Mini App sign-in has its own entrypoint, @domino-sdk/relay/telegram (experimental): withTelegram(relay) adds relay.telegram to a client and answers the same client, relay.telegram.miniApp.signIn(initData) and .connect(initData) do what relay.auth.telegram.miniApp did, and telegramMiniAppInitData() reads the launch data. Apps that don't import it don't carry it. relay.auth.telegram.miniApp and the telegramMiniAppInitData exports of @domino-sdk/relay/browser and @domino-sdk/relay/react are deprecated, keep working through 1.x and are removed in 2.0.0.

    Telegram Login and Mini App sign-in now run in the Telegram plugin, with no change to what they accept or answer: members linked before keep signing in as the same member, and /v1/auth/telegram/mini-app takes the same requests. management.plugins.list() and domino connections get telegram list one more slot for Telegram, which knownPluginSlots adds: identity (members sign in with it).

  • 52ed037: Telegram's trackers and ctx.telegram now come from the Telegram plugin, with no change to what they record or read: telegram.messages() and telegram.reactions() trackers, the bot's webhook at /v1/integrations/telegram/updates, and ctx.telegram.connection() and member() behave as before, and published quests keep working. management.plugins.list() and domino connections get telegram list two more slots for Telegram, which knownPluginSlots adds: tracker-source (the plugin writes its trackers' records) and evaluator-capability (quest code reads it as ctx.<plugin>). A quest may now declare the telegram integration module, { provider: "telegram" }, and publishes; before, publishing refused it with unknown-integration-module. Declaring it is optional, since every quest gets ctx.telegram.

  • Updated dependencies [a341b16]

  • Updated dependencies [bd622ae]

  • Updated dependencies [76be180]

  • Updated dependencies [5233be5]

  • Updated dependencies [ee3f488]

  • Updated dependencies [3665a4e]

  • Updated dependencies [f13cfb6]

  • Updated dependencies [361a5b1]

  • Updated dependencies [154af63]

  • Updated dependencies [79b7c68]

  • Updated dependencies [eed1808]

  • Updated dependencies [56a0601]

  • Updated dependencies [5cc0d4d]

  • Updated dependencies [94ca2b5]

  • Updated dependencies [f9c9f34]

  • Updated dependencies [dcc32da]

  • Updated dependencies [98ad384]

  • Updated dependencies [4bf82e5]

  • Updated dependencies [4bf82e5]

  • Updated dependencies [4bf82e5]

  • Updated dependencies [d4fcade]

  • Updated dependencies [8064983]

  • Updated dependencies [980e6ca]

  • Updated dependencies [d683d00]

  • Updated dependencies [081806c]

  • Updated dependencies [114e0c7]

  • Updated dependencies [92ee725]

  • Updated dependencies [ea2c9be]

  • Updated dependencies [2de5d71]

  • Updated dependencies [e58c2ae]

  • Updated dependencies [3a3feef]

  • Updated dependencies [10826dc]

  • Updated dependencies [c391c86]

  • Updated dependencies [7a93f76]

  • Updated dependencies [1a70098]

  • Updated dependencies [dd7ced3]

  • Updated dependencies [5a34b36]

  • Updated dependencies [4b7c062]

  • Updated dependencies [de99154]

  • Updated dependencies [80e09c2]

  • Updated dependencies [1c61073]

  • Updated dependencies [423b6fc]

  • Updated dependencies [01b789e]

  • Updated dependencies [2a83792]

  • Updated dependencies [172c3b5]

  • Updated dependencies [aaf28f9]

  • Updated dependencies [b2b8127]

  • Updated dependencies [a381dde]

  • Updated dependencies [9f39ea4]

  • Updated dependencies [b866593]

  • Updated dependencies [1cdc64b]

  • Updated dependencies [3d2f654]

  • Updated dependencies [4443124]

  • Updated dependencies [b9270ff]

  • Updated dependencies [90a20c6]

  • Updated dependencies [8e8e24a]

  • Updated dependencies [52ed037]

  • Updated dependencies [2fe2d72]

  • Updated dependencies [7fa2a06]

  • Updated dependencies [06046f1]

    • @domino-sdk/relay@1.62.0

1.61.0

Minor Changes

  • e71f881: domino check runs each badge, point ledger, offer, leaderboard, referral, dashboard and tracker entry in a separate Node process and parses its export with the schema Relay parses it with at deployment. A definition that fails, such as an offer without stock, reports a DEFINITION_INVALID error diagnostic with its file, export, field path and message, and fails the check instead of failing later in staging. defineOffer, defineBadge, defineLedger, defineLeaderboard, defineReferral, defineDashboard and defineTracker still throw a ZodError for invalid input, and its message now names the kind, key and fields, for example Offer "ladder" is invalid: stock: Invalid input: expected number, received undefined. Staging shows that message when its data setup fails. A deployment, preview or staging activation with an inline or described definition that fails its schema now answers 422 validation-failed with that message, where it answered 400 Invalid request.

  • b369a75: A project connects a Discord server that another project of its organization already connects, ready, in the same environment, without installing Domino's bot again. Any credential with publish permission can: an automation key, a CLI session, a personal API key or Console (Integrations, Or reuse). Installing the bot into a new server stays with an organization administrator in Console.

    • management.discord.servers() and domino integrations discord servers list the servers the project can connect.
    • management.discord.connect(server) and domino integrations discord connect --server <id> connect one, once the bot still sees it. They refuse with 409 server-not-connected-in-organization for any other server, 409 integration-connected when the project already has a Discord connection, and 422 server-not-reachable when the bot lost the server. Connecting the server the project already connects changes nothing.
    • management.discord.connection() and domino integrations discord status read the project's Discord connection.

    The project's audit records each connection as discord-server-connected, with who connected it and the project whose connection it reused. An automation key and a CLI session connect only in test.

  • d657954: domino stage warns when the app signs participants in with a wallet but the test environment has no wallet family enabled, which leaves the app's wallet sign-in doing nothing. The warning goes to stderr and, as wallet-sign-in-off with the enabling domino api PUT /participant-sign-in command, into the JSON result's warnings; the build still stages. domino doctor --remote reports the same as its wallet-sign-in check. The CLI reads wallet sign-in from each of the app's source files: relay.auth.wallet.signIn, useSignIn().withWallet, useSignInWallets, Cosmos chains passed to useSignInOptions, a wallet option picked with find, or wallet options filtered without OAuth, email or Telegram ones. The starters' sign-in, which renders every option the project offers, does not count.

Patch Changes

  • 97b71e2: The domino skill's error reference no longer lists organization-not-provisioned: Relay serves every organization, so no request is refused with it.
  • Updated dependencies [e71f881]
  • Updated dependencies [f0ce453]
  • Updated dependencies [b369a75]
    • @domino-sdk/relay@1.61.0

1.60.0

Minor Changes

  • d258795: A budget can cap what one person gives, not only what one member receives. budget({ key, scope: "author", period, limit, cost }) counts per tracker record author: the account that caused the record, such as a reaction's reactor, whether or not a member linked it. Put it in a tracker quest's budgets beside a "member" budget to cap both sides, for example a tip paid to a message's author with beneficiary: "messageAuthor": at most 20 tips a day per receiver and 10 a day per reactor. A tip over either cap is rejected with budget-exhausted, permanently; other reactors' tips keep paying. It works on telegram.reactions(), discord.reactions(), kudos recipients and X inReplyTo replies. Author budgets share keys, calendar periods and the atomic commit with the other budgets, and imported completions don't count toward them.

    An author budget needs a tracker trigger. domino check reports AUTHOR_BUDGET_TRACKER_REQUIRED on another quest, domino build and domino deploy refuse it, and Relay refuses its publication.

    The types SDK 1.x code reads are unchanged: Budget keeps scope: "member" | "project", and a quest's description lists author budgets in the new optional authorBudgets (AuthorBudget, authorBudgetSchema from @domino-sdk/relay/schema), apart from budgets. budget() returns an AuthorBudget for scope: "author". A member's quest budgets (QuestBudgetView) leave author budgets out, since they cap someone else's giving.

  • c619bf3: New domino deploy rollback [deployment] publishes the previous published test deployment again, undoing the latest domino deploy. It names both deployments and asks before publishing (--yes skips that), and it saves the change under an operation ID, so domino operations resume finishes it once after an uncertain response. Only the previous published deployment can be rolled back to. Only what it contains goes back, and the staged build does not change; once a hosted app build has published to test since that deployment, the rollback is refused with 409 conflict. In live it fails with ENVIRONMENT_UNSUPPORTED and apiCode: "promotion-required". management.deployments.rollback now works with a test client too, publishing at once; in live it still submits the rollback for review.

  • e899f47: Member imports carry referrals. A row's optional referral names the member's inviter (another row's ref, or the sign-in identity of an existing member), when they accepted, and the referral programs they already qualified in (qualified: [{ program, at }]). Applying records the referral and the qualifications without paying any referral reward, milestone or bonus; they count toward the inviter's qualified friends, and the friend's later points pay the program's bonus. An inviter's row applies before the rows that name it. New refusal codes: unknown-inviter, self-referral, inviter-already-accepted, referral-cycle, inviter-refused and unknown-referral-program. The check summary adds referrals (accepted, and qualified by program), management.referrals.participants() names the import that recorded a relationship or qualification in import, and management.members.get() returns the member's accepted referral in an optional referral (inviter, acceptedAt, suspended, import).

  • 8ee2000: A member import vouches for Telegram and Discord accounts too. A row's accounts takes { kind: "telegram", subject } and { kind: "discord", subject }, each a numeric user ID, at most one of each per member, from domino import and management.memberImports.addRows. They work like a vouched X account: Telegram and Discord tracker records, quest checks and Discord role checks trust them from when the import attached them, nobody else can link them, and the member's own Telegram or Discord link replaces them. They never sign in: a Telegram or Discord sign-in with one is refused with sign-in-method-disabled. A row naming a second account of a kind, or another one than the member's, is refused with the new MemberImportRefusalCode.TelegramAccountSelected (telegram-account-selected) or DiscordAccountSelected (discord-account-selected).

    @domino-sdk/relay/server adds memberImportVouchedAccountSchema, memberImportFullRowSchema and memberImportFullRowsInputSchema, with the types MemberImportVouchedAccount, MemberImportFullRow, MemberImportFullRowInput and MemberImportFullRowsInput, for rows with every kind of vouched account; addRows takes MemberImportFullRowInput[]. memberImportAccountSchema, memberImportRowSchema and memberImportRowsInputSchema stay the X-and-wallet form. Member identity states carry imported: true for an X, Discord or Telegram account an import attached, and so does a connected ManagedTelegramAccount.

    An imported X, Telegram or Discord account now pays tracker records from the moment it was imported. Before, a vouched X account had no link time, so its records were skipped as historical. Members imported before this release keep that unknown link time until they link their X account themselves.

  • f0d5b8e: Offers can be archived and restored. management.offers.archive and unarchive (POST /management/v1/offers/OFFER/archive|unarchive), domino offers archive|unarchive OFFER --reason, Console Settings → Resources and the offer's card in Console Rewards archive one in test, and a promotion carries it to live. Each returns { resource, changed }, typed by the new offerLifecycleResultSchema, with resource.kind offer. An archived offer leaves member offer reads and refuses redemption with offer-paused; management reads keep it, with the new archivedAt field on Offer, and its redemptions can still be canceled. management.resources.list() and domino resources list list offers in a new offers array (managedResourceListSchema), beside the existing kinds. A rollback now archives an offer the earlier promotion did not carry.

  • ea1f033: Revert an applied or stopped member import: management.memberImports.revert(id, { reason }) starts it, and domino imports revert IMPORT --reason TEXT starts it and waits. In the background, one row at a time, it reverses the applied rows' opening balances (taking only points members still have; unreversedPoints lists the rest), revokes their imported completions (reason import-reverted, freeing the occurrence) and removes the wallets, accounts and identities they linked unless the member signed in with one since (keptLinks). Members stay in the project. A reverting or reverted import reads as applied with revert set, and is reverted once; the same actionId answers the import again. Rows applied before imports recorded their links keep them (linksUntracked). Reverting needs publish permission and works in live from API keys and CLI sessions too. Console shows Revert import on applied and stopped imports. MemberImport gains revert: the reason, who asked and when, rows reverted so far, then revertedAt and the outcome.

  • 9a7ad11: Stop a member import while it applies: management.memberImports.stop(id) and domino imports stop IMPORT let the row being applied finish, apply no more rows, and end the import: it reads as applied with appliedAt null and stoppedAt and stoppedBy set, and its remaining rows stay ready. A stopped import can't be applied again; import the remaining rows in a new import. Stopping needs publish permission and works in live from API keys and CLI sessions too. Console shows Stop import on an applying import. MemberImport gains stoppedAt and stoppedBy; check stoppedAt to tell a stopped import from a fully applied one.

  • df3a08c: A test member's clock moves by hours and minutes, or to an instant, so time-of-day rules can be tested: block edges, or midnight in the project's time zone. management.testMembers.advance(member, input) takes { actionId } with one of days (1–30, unchanged: project-calendar days that keep the time of day), hours (1–720), minutes (1–43200) or to, an ISO 8601 instant with an offset that must be later than the member's clock (else 422 test-member-clock-past). From the CLI, domino test-members advance MEMBER takes one of --days, --hours, --minutes or --to. The 90-day limit holds across units (422 test-member-clock-limit): days still add up to at most 90 and land no later than the 90th date, and hours, minutes and to never move the clock past 90 days from now. offsetDays still counts the days advanced with days; hours, minutes and to leave it unchanged, so read the member's date from today. @domino-sdk/relay/server exports testMemberClockAdvanceSchema and TestMemberClockAdvanceInput for every form; testMemberAdvanceSchema and TestMemberAdvanceInput stay the days form.

Patch Changes

  • 6b5228b: Quests can announce a completion in a connected Telegram chat or Discord channel. List telegram.postMessage({ chat, text, replyToTriggeringMessage }) or discord.postMessage({ channel, text, replyToTriggeringMessage }) in the new optional announcements of defineQuest, beside rewards (at most three; an array or a function of the settings). The text is a template with the placeholders {beneficiary}, {author}, {points}, {rewards} and {quest} (announcementPlaceholders); people are mentioned through their linked account on that platform. Relay posts after the rewards commit, as a reward delivery with the new RewardDeliveryAction.PostMessage action and an empty roleId, retried with backoff and paced per chat; a failed post never undoes the rewards, and management.retryRewardDelivery(id) retries one. Descriptions carry them in the new optional announcements field (announcementSchema). discord.channels({ postable: true }) lists channels the bot can't post in as unavailable, and TelegramChat gains an optional canPost. The CLI's authoring reference documents announcements.
  • Updated dependencies [d258795]
  • Updated dependencies [d41f5da]
  • Updated dependencies [e899f47]
  • Updated dependencies [8ee2000]
  • Updated dependencies [f0d5b8e]
  • Updated dependencies [6b5228b]
  • Updated dependencies [ea1f033]
  • Updated dependencies [9a7ad11]
  • Updated dependencies [df3a08c]
    • @domino-sdk/relay@1.60.0

1.59.0

Minor Changes

  • 1d5a195: management.attempts.acceptAnyway({ attempt, reason }) (POST /attempts/<id>/accept-anyway, review permission) accepts a rejected attempt anyway, for any member, reversing a reviewer's or an evaluator's rejection. The acceptance pays as a reviewer's approval would, with the normal completion.created webhook; the rejection stays in the attempt's events beside a staff-decision with decision: "accept-anyway" and the reason, and the retry cooldown ends. Only the member's latest attempt on a quest that isn't archived qualifies (409 attempt-superseded, quest-archived), and a cap the acceptance would break refuses it instead of rejecting it again (409 completion-cooldown, budget-exhausted, completion-revoked, already-completed). management.attempts.get answers supersededBy, the member's newer attempt on the quest, or null. The CLI adds domino attempts accept-anyway ATTEMPT --reason TEXT.

  • a0c77e4: Archiving or unarchiving a group, pausing or resuming a tracker, and changing the project's schedule state take an optional reason, which the audit log records: management.groups.archive(group, { reason }) and unarchive(group, { reason }), management.trackers.pause(reference, { actionId, reason }) and resume, and management.schedules.setState(state, { reason }). A reason is a trimmed string of 1–500 characters, like the other lifecycle reasons; leaving it out works as before. The CLI adds --reason TEXT to domino groups archive|unarchive, domino trackers pause|resume and domino schedules pause|archive|resume. Console asks for a reason in a dialog for each of these, and the project schedule menu no longer applies a state as soon as you pick it.

  • 424863f: Operators can restore an entitlement they voided. management.entitlements.restore(entitlement, { revision, reason }) (POST /management/v1/entitlements/<id>/restore, review permission) and domino entitlements restore <id> --revision <n> --reason <text> make a void entitlement available again at the tier an accepted completion still earns, and bring back any referral review the void settled. Restoring is refused with 409 entitlement-status when a completion revocation voided the entitlement or no accepted completion still earns it (the body names the completion to restore instead), when canceling an offer redemption voided it, or when a referral reward was voided before voids kept their reviews. Console Rewards and member detail offer Restore reward on a voided reward, and Audit history lists each restore with its reason.

  • 790aa5d: Operators can restore a completion they revoked by mistake. management.completions.restore({ completion, reason }) (POST /management/v1/completions/<id>/restore, review permission) and domino completions restore <completion> --reason <text> re-apply what the revocation recorded, not a fresh reward evaluation: the points it reversed are awarded again (points it could not reverse are not), its badges, group memberships, tiered rewards, Discord role grants, referral qualifications and streak milestone come back, and budgets stay spent. The result (CompletionRestoration) lists what came back. Restoring is refused with 409 completion-not-revoked when the completion isn't revoked, and with 409 occurrence-taken (naming the completion that holds it) when the revocation freed the occurrence and the member completed it again. members.get(id).revokedCompletions lists up to 25 revoked completions, each with its revoke's reason and result. Console member detail and attempt detail offer Restore completion, and the attempt history and Audit history show the restore beside the revoke.

  • 95cb8d8: On-chain action checks on Solana. ctx.wallet.transactions({ program, since, afterActivation?, mint?, address? }) reads the successful, finalized transactions of the member's linked Solana wallets since since that invoked program in a top-level or inner instruction, oldest first. It returns { program, chain, mint, since, at, complete, transactions: [{ wallet, signature, slot, timestamp, programs, balanceChanges: [{ mint, delta }] }], observedAt } (WalletTransactions, WalletTransaction, WalletBalanceChange, walletTransactionsSchema), with mint "SOL" for lamports (rent included, the fee the wallet paid left out) and delta a signed integer string, or null when the member has no linked Solana wallet, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. With mint, only each wallet's associated token account for that mint is read. since may be at most 30 days before the evaluation (maxWalletTransactionLookback); each wallet's read fetches at most 1,000 transactions (maxWalletTransactions), and a wallet with more returns its newest ones with complete: false instead of throwing. WalletReader.transactions is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The program comes from the new settings.solanaProgram({ label, default }) setting, a base58 address (SolanaProgram, isSolanaProgram) stored as a text setting with picker: "solana-program". Publication, Console settings edits and unarchiving check that the account exists on Solana mainnet and is executable.

    New pure helpers in @domino-sdk/relay/authoring read one transaction: received(tx, mint) and spent(tx, mint) return the wallet's gain or loss as an integer string, or "0"; swapped(tx, { into?, from? }) checks that one mint went down and a different one went up; invoked(tx, program). The testing kit answers ctx.wallet.transactions() from facts.wallet.transactions.

Patch Changes

  • aaa07ad: The SDK and CLI depend on Zod 4.6.5, whose schemas keep their methods on a shared prototype instead of binding about 7 KB of them to every schema instance. Importing the SDK takes far less memory: a Relay backend process that loads the server entry starts with about half the heap. Quest settings.datetime() values, member-import times and X post createdAt values (including hand-written testing fixtures) still accept minute-precision instants such as 2026-11-01T00:00Z, which Zod 4.6 alone rejects. An X post read without a media field still gives an empty list.

    One tracker data schema change after a rebuild: a field declared as z.number().min(0).int() now stores minimum: 0, so Relay refuses negative values at ingestion that the app's own schema already refused. Zod 4.3 stored the safe-integer minimum for that order of checks.

  • Updated dependencies [1d5a195]

  • Updated dependencies [a0c77e4]

  • Updated dependencies [424863f]

  • Updated dependencies [790aa5d]

  • Updated dependencies [850a252]

  • Updated dependencies [7ac13ef]

  • Updated dependencies [5b94440]

  • Updated dependencies [95cb8d8]

  • Updated dependencies [aaa07ad]

    • @domino-sdk/relay@1.59.0

1.58.0

Not published to npm. Its changes first reached npm in 1.59.0.

Minor Changes

  • 88776b6: The operations journal no longer keeps webhook signing secrets. domino webhooks create and domino webhooks rotate-secret still save the secret while the operation is incomplete, so domino operations resume returns the same one, but once the CLI has written it to stdout the saved result has secretRedacted: true instead, domino operations show adds a note saying to rotate it, and repeating the command with the same --operation-id fails with CONFLICT. A secret an older CLI saved is printed by the next show or resume, then removed. New domino operations forget <id> removes one saved operation, and domino operations prune removes completed ones last changed more than --older-than days ago (default 30). domino quests replay --out now writes the capture to a new file with mode 0600 that replaces the destination, since it holds raw provider answers.

Patch Changes

  • Updated dependencies [321763b]
  • Updated dependencies [a2318dd]
    • @domino-sdk/relay@1.58.0

1.57.0

Not published to npm. Its changes first reached npm in 1.59.0.

Minor Changes

  • 7012d1c: Contract state and Cosmos staking reads in quest evaluators.

    ctx.wallet.call({ call, args?, address? }) calls one view or pure EVM contract function once per linked EVM wallet, every call at one block, through dRPC on the chains management.walletBalanceChains() lists: state a contract keeps per address, such as stakedOf(address), getVotes(address) or userInfo(uint256, address). { wallet: true } in args stands for each wallet's address. It returns { call, chain, contract, returns, total, wallets: [{ address, value }], at, observedAt } (WalletCall, walletCallSchema), with integers as decimal strings and total their sum (null for bool and address returns), or null when the member has no linked EVM wallet. A revert is an author-error carrying the contract's reason. The call comes from the new settings.contractCall({ label, default }) setting (picker: "contract-call"): the contract's CAIP-10 ID and a human-readable signature, such as eip155:8453:0x… function stakedOf(address) view returns (uint256) (ContractCall, isContractCall, parseContractCall, parseContractFunction). Only view or pure functions with elementary parameters that return one uint, int, bool or address are accepted; publication, Console settings edits and unarchiving also check the chain.

    ctx.wallet.delegations({ chain, validator?, address? }) reads the native staking delegations of the member's wallets linked on a Cosmos SDK chain Relay lists, at one block, in base units of the chain's staking denomination: { chain, denom, validator, total, wallets, validators, at, observedAt } (WalletDelegations, walletDelegationsSchema), optionally only to one validator, or null when the member has no wallet on that chain.

    Both follow ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. WalletReader.call and WalletReader.delegations are optional in the type so that existing test doubles compile; Relay supplies them to every evaluation, and the testing kit answers them from facts.wallet.call and facts.wallet.delegations. New contract-state and cosmos-staker snippets accept members whose view call reaches a minimum, or who delegate at least a minimum, optionally to a configured validator.

  • 59a01d8: On-chain action checks on EVM chains. ctx.wallet.events({ event, account, since, afterActivation?, where?, address? }) reads the events of one contract whose indexed address argument account is one of the member's linked EVM wallets, from since to the chain's finalized block (its safe block on a chain without finality), through dRPC eth_getLogs on the chains management.walletBalanceChains() lists. It returns { event, chain, contract, since, at, finality, logs: [{ wallet, block, timestamp, tx, logIndex, args }], observedAt } (WalletEvents, walletEventsSchema) with decoded arguments, or null when the member has no linked EVM wallet, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. where holds equality or { gte } conditions on other arguments (WalletEventCondition); afterActivation skips events before the quest's activation. since may be at most 30 days before the evaluation (maxWalletEventLookback), and a read over more than 3,000,000 blocks or 1,000 logs (maxWalletEventLogs) throws author-error. WalletReader.events is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The event comes from the new settings.contractEvent({ label, default }) setting: a CAIP-10 contract, a space and the event's Solidity signature with argument names and indexed markers, such as eip155:8453:0x… Transfer(address indexed from, address indexed to, uint256 value) (ContractEvent, isContractEvent, parseContractEvent, ContractEventParts), stored as a text setting with picker: "contract-event". An event without an indexed address argument is refused. Publication, Console settings edits and unarchiving also check that the chain reports a finalized or safe block and that the address holds a contract.

    The testing kit answers ctx.wallet.events() from facts.wallet.events. A new onchain-action snippet rewards each new matching event since the later of the quest's activation and the wallet's link, with an optional minimum amount, keyed on chain:tx:logIndex.

  • 956b721: NFT holding checks on EVM chains and Solana. ctx.wallet.nfts({ collection, address? }) counts the NFTs of one collection the member's linked wallets of its family hold: on EVM, an ERC-721 contract's balanceOf(owner), or one ERC-1155 token ID's balanceOf(owner, id), through dRPC on the chains management.walletBalanceChains() lists; on Solana mainnet, the NFTs whose Metaplex metadata names the collection with verified: true (SPL Token and Token-2022 accounts holding exactly 1 of a 0-decimal, supply-1 mint; compressed NFTs and Metaplex Core assets are not counted). It returns { collection, chain, standard, total, wallets: [{ address, count }], at, observedAt } (WalletNfts, walletNftsSchema; standard is "erc721", "erc1155" or "metaplex"), or null when the member has no linked wallet of that family, and follows ctx.wallet.balance()'s capture, author-error and provider-unavailable rules. WalletReader.nfts is optional in the type so that existing test doubles compile; Relay supplies it to every evaluation.

    The collection comes from the new settings.nft({ label, default }) setting, a CAIP-19 asset type such as eip155:1/erc721:0x…, eip155:8453/erc1155:0x…/1 or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:<collection mint> (NftCollection, isNftCollection, parseNftCollection, NftCollectionParts), stored as a text setting with picker: "nft". Publication and Console settings edits check it: an EVM chain and that the contract reports the standard through ERC-165 supportsInterface, so a token contract or the wrong standard is refused when it is set; a Solana mint must be a Metaplex collection NFT, so a fungible mint, a mint without Metaplex metadata, a print edition or a member NFT is refused. Unarchiving checks it too, and a platform without its dRPC key refuses every EVM collection. ctx.wallet.balance() pointed at an ERC-721 or ERC-1155 contract now throws author-error naming ctx.wallet.nfts() instead of reading a token count; it asks ERC-165 alongside decimals() on every token read.

    The testing kit answers ctx.wallet.nfts() from facts.wallet.nfts. A new nft-holder snippet accepts members whose linked wallets hold at least one NFT from the configured collection, on EVM or Solana.

  • 1770ada: A seventh starter, loyalty: members join a campaign, earn points from its steps, join a members' group through a quest's group membership reward, and spend points on offers, one of them limited to the group with inGroup. Console offers it and domino create --template loyalty creates it.

    Starters are now an open list, so adding one no longer changes the SDK's types. starters lists every starter as a Starter with a plain string id, offeredStarterSchema checks an ID against it, and projectCreateInputSchema and repositoryCreateSchema accept any offered starter; management.createProject uses the first. starterTemplates, StarterTemplate, StarterId, starterIdSchema, projectInputSchema and createRepositorySchema are deprecated: they keep the first six starters and are removed in 2.0.0. To upgrade, replace starterTemplates with starters and starterIdSchema with offeredStarterSchema, and type a starter ID as string.

  • cec850f: Create and change a tracker of every managed source without a deployment, not only X search. management.trackers.create and management.trackers.update (POST /trackers, POST /trackers/:tracker/update) take a source: { kind: "x-search", query }, { kind: "discord-messages", text? }, { kind: "discord-reactions" }, { kind: "telegram-messages" }, { kind: "telegram-reactions" }, { kind: "youtube-shorts" } or { kind: "tiktok-videos" }. Each is stored as the source its builder makes, such as telegram.reactions(), so quests read a data tracker as they read the same tracker in code. query alone still creates an X search tracker; create takes exactly one of query or source. An update's source replaces the source's settings but never its kind, which is refused with 422 validation-failed. Turning Discord message text off erases the text already stored. managedTrackerCreateSchema validates the new input, while trackerCreateSchema keeps its X-only shape. managedTrackerSource(source) reads a published source by these names, and managedTrackerSourceKinds lists them.

    domino trackers create KEY --source <kind> creates one from the CLI, with --text on for Discord messages; domino trackers update TRACKER --text on|off turns message text on or off. Console Trackers → New tracker offers every source.

  • 94726d8: Organization automation keys (dak_): a credential for CI and headless coding agents that no person owns. An organization administrator creates one in Console Settings → API keys for every project or a chosen list, with chosen permissions and an optional permission to create projects. It works as RELAY_MANAGEMENT_TOKEN, with domino login --token-stdin and the Git credential helper, in the test environment only: live writes, administration and minting other credentials are refused.

    The management client adds automationKeys (list, create, addSecret, revokeSecret, revoke) with automationKeyInputSchema, automationKeySchema, issuedAutomationKeySchema and automationKeyPermissionSchema. accessSchema gains an optional credential that /access and domino whoami --json fill for an automation key with its kind and scope.

  • 6d9f0de: telegram.messages() is a tracker source of the messages members post in the project's connected Telegram groups and supergroups, one record per message with the poster's Telegram account as author. data is TelegramMessageData: chatId, chatType, messageId, replyToMessageId and threadId, never the text. Bots', service and channel messages are not recorded. In SDK 1.x it is a custom source on the reserved namespace telegram-messages, which Relay alone writes. The new telegram-contribution snippet rewards posting once per member per day.

  • 35b445c: Unattended agent mode. Whoever launches an agent with nobody present sets DOMINO_AGENT_MODE=unattended, and domino doctor --json and domino whoami --json report it as agentMode. In that mode browser domino login refuses with the new UNATTENDED_LOGIN_UNAVAILABLE code, so remote work uses RELAY_MANAGEMENT_TOKEN, and domino feedback appends each report to ./domino-feedback.jsonl (or DOMINO_FEEDBACK_FILE) instead of sending it; --submit or DOMINO_FEEDBACK=submit sends it. The Domino skill builds from the supplied brief without waiting, records the gaps it fills in CAMPAIGN.md, and ends with a report.

  • 8d11f64: wallet.balances({ chain, token }) is a tracker source of daily balance samples that Relay writes itself. Once per project-calendar day, at a time members can't predict, Relay reads one token's balance in every wallet linked by a member who joined the project, every wallet of a run at one pinned EVM block or Cosmos height (on Solana, every read must reach the run's pinned slot). chain and token are those of ctx.wallet.balance(). Each record is one wallet, keyed by its CAIP-10 account on the sampled chain, which is also its author; each sample is a new observation whose occurredAt is the sample time and whose data is WalletBalanceSample (amount in base units, decimals, block). A provider outage retries the same block later that day and never writes a zero, so a day with no successful sample breaks a streak({ days }). Reward holding for days with automatic({ tracker, where: { path: "amount", gte: { setting: "threshold" } } }) and streak({ days }). In SDK 1.x it is a custom source on the reserved namespace wallet-balances, with the chain and token in the new optional balance field of custom sources (walletBalanceSourceSchema); ingestion refuses the namespace, and a project and environment can have at most 5 such trackers (limit-reached).

    management.trackers.create and Console New tracker take { kind: "wallet-balances", chain, token } (managedTrackerSourceSchema), and domino trackers create --source wallet-balances takes --chain and --token. The new token-hold-streak snippet rewards holding a token for 7 days in a row.

Patch Changes

  • dbffafb: On a Node release older than 24 the CLI now prints Domino CLI needs Node 24 or newer. You have <version>. and exits 1, or the NODE_VERSION_UNSUPPORTED error with --json, instead of crashing with a SyntaxError. A relay.json that still has a starter's placeholder organization (local) and no checkout scope is refused with the new SCOPE_PLACEHOLDER code, which says to run domino checkout or pass --organization and --project; domino doctor reports it as a scope error. whoami, login, logout, create, checkout, init and feedback ignore the placeholder instead of sending it.
  • 1e2de20: Agents can act as test members on staging. A staging link (domino api POST /staging/link) minted by a domino login session, or by a personal API key or organization automation key that holds publish, now opens the test toolbar, acting as that person or as automation:<id>. Domino checks the key again on every toolbar request, so revoking it ends access. The Domino skill's campaign testing reference adds "Check participant flows on staging": mint the link, open it, sign the page in as a test member, and walk the app's own screens.
  • Updated dependencies [7012d1c]
  • Updated dependencies [59a01d8]
  • Updated dependencies [956b721]
  • Updated dependencies [1770ada]
  • Updated dependencies [cec850f]
  • Updated dependencies [94726d8]
  • Updated dependencies [6d9f0de]
  • Updated dependencies [8d11f64]
    • @domino-sdk/relay@1.57.0

1.56.0

Minor Changes

  • 9ec9f2f: Publishing creates the groups code names. A deployment whose quest groupMembership(group) reward or inGroup(group) prerequisite, offer inGroup(group) requirement, leaderboard group or referral inviterGroup names a group the environment doesn't have now creates it, with no members and the defineGroup handle's title (the key when only a leaderboard or referral program names it), instead of refusing with 422 validation-failed. Approving a promotion creates the groups live lacks the same way. The deployment preview and result, and the prepared promotion, list each such group as a group-created warning, and the audit history records group-created with the deployment or promotion as its reason. An existing group keeps its title and members, and publishing never unarchives one: a groupMembership reward naming an archived group still refuses. A project whose code uses groups no longer needs domino groups create before its first deployment or promotion.
  • fbe8e64: Offers take prerequisites. defineOffer({ ..., requires }) accepts the prerequisites quests do, completed(quest), balanceAtLeast(ledger, minimum) and inGroup(group), at most 30; offerDefinitionSchema and offerAuthoringSchema gain an optional requires. Publication refuses a required quest or ledger the deployment doesn't know. Redeeming as a member who misses one fails with 409 prerequisites-incomplete and missing, the shape quest admission uses, before any points or stock are spent. Offer gains requires and missing: for a signed-in member, relay.offers.list() and useOffers() list the prerequisites they have not met, empty for a read without a member or an offer without requires. availability keeps its four kinds. Console Rewards shows an offer's requirements on its card, and a promotion carries them to live.
  • cba70f4: Add the organization's billing read. management.billing.organization() and domino billing organization (GET /management/v1/organization/billing) are open to any member of the organization and need no project. They list the projects the caller may read, each with its plan, live and liveState as management.usage.plan() reports them, and its Stripe subscription. They also return the organization's Free projects against their limit (freeProjects), the free AI allowance its Free projects share (freeAi), the monthly AI allowance across the organization, the trial, whether plan limits are enforced, and whether the caller may open the customer portal (billing). management.billing.organizationPortal() (POST /management/v1/organization/billing/portal) opens the customer portal as management.billing.portal() does, returning to Console's organization billing page. organizationBillingSchema and the OrganizationBilling type are exported from @domino-sdk/relay/server, with a reader in managementResponses.
  • 6458ebe: Add the project's plan read. management.usage.plan() and domino usage plan (GET /management/v1/usage/plan, read permission) return the project's plan and where it came from (source), whether it may serve live and why (live), live's notice or offline state (liveState), the organization's one live trial (trial), whether the plan includes a custom domain, the AI builder balance by pool with when it renews (ai), the Stripe subscription as management.billing.read() reports it, and whether plan limits are enforced. Unlike management.billing, it is open to anyone who can read the project. projectPlanSchema and the ProjectPlan type are exported from @domino-sdk/relay/server, with a reader in managementResponses.
  • 62c3791: Add TikTok creator accounts and the tiktok.videos() tracker source. A signed-in member links their TikTok account through TikTok's consent screen with relay.auth.creatorAccounts.connect("tiktok") (useCreatorAccounts() in React); the account's subject is its open_id, its profile a display name and avatar, and knownCreatorAccountProviders now lists "tiktok". Operators read and unlink it like a YouTube channel. tiktok.videos() from @domino-sdk/relay/authoring reads the public videos on the accounts members linked, only with each member's own token, every refreshSeconds of the tracker, as content with a TikTokVideoBody (title, description, durationSeconds, width, height, createdAt, shareUrl, isAigc) and the metrics views, likes, comments and shares, so content() quests, performance and engagement rules use it. A known video the account no longer returns is a content removal; an account whose token needs reconnecting is not read, as provider-unavailable. It is a custom source on the reserved namespace tiktok-videos, which ingestion refuses. Both are inert until Domino configures TikTok. The tiktok-videos capability and the participant and authoring references describe them.
  • a533f93: Add creator accounts and the youtube.shorts() tracker source. A signed-in member links the YouTube channel they publish from as a creator account: relay.auth.creatorAccounts.list() (GET /v1/auth/creator-accounts), connect(provider) and disconnect(provider), and useCreatorAccounts() in React. A creator account is never a sign-in method, linking another channel replaces the current one, and the account's subject is the channel ID picked on Google's consent screen. Each entry mirrors AccountConnection with the channel's profile; provider is an open string, with knownCreatorAccountProviders in @domino-sdk/relay/schema listing "youtube", so CreatorAccount, CreatorAccounts and their schemas sit beside SDK 1.x's closed provider lists. Operators read them on management.members.get(member).creatorAccounts and management.members.creatorAccounts(member), and management.members.unlinkCreatorAccount(member, { provider, subject, revision, reason }) unlinks one with review permission; managedCreatorAccountSchema, managedCreatorAccountsSchema and memberCreatorAccountUnlinkInputSchema are exported from @domino-sdk/relay/server. youtube.shorts() from @domino-sdk/relay/authoring reads the Shorts on the channels members linked, every refreshSeconds of the tracker, as content with a YouTubeShortBody (title, description, tags, durationSeconds, publishedAt) and the metrics views, likes and comments, so content() quests, performance and engagement rules use it. It is a custom source on the reserved namespace youtube-shorts, which ingestion refuses. Both are inert until Domino configures YouTube. The creator-accounts and youtube-shorts capabilities and the participant and authoring references describe them.

Patch Changes

  • 42784fb: The hosted-app skill reference notes that every domino.page address answers /.well-known/security.txt itself with Domino's abuse contact, so an app never receives that path there; a custom domain still forwards it.
  • a88ba4d: The capability map and the Domino skill's automation reference describe Domino's MCP server at https://mcp.domino.run, which operators connect to Claude or ChatGPT: reads of projects, quests, campaigns, submissions, members, leaderboards, offers, usage, analytics and audit history in test and live, and test-member and review tools that write in test only. See domino capabilities mcp-server --json and the "Claude and ChatGPT" section of domino reference automation.
  • Updated dependencies [9ec9f2f]
  • Updated dependencies [fbe8e64]
  • Updated dependencies [cba70f4]
  • Updated dependencies [6458ebe]
  • Updated dependencies [62c3791]
  • Updated dependencies [a533f93]
    • @domino-sdk/relay@1.56.0

1.55.0

Minor Changes

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

    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.

  • 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.

  • 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.

    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.

    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.

  • 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.

    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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

Patch Changes

  • 283a1a2: The Domino skill has an analytics reference (domino reference analytics): how to answer "how is it going" from domino analytics get and domino analytics query, choose catalog metrics from a project's goals, and build, check and explain dashboards. The project-analytics and dashboards capabilities point to it. The referral starter ships a Referrals dashboard with sign-ups, qualified referrals by program and the qualification rate.

  • 21a4386: domino check, domino build and domino deploy refuse quests that declare one budget key with a different scope, period, limit or cost, naming both quests and the fields that differ. check reports it as the error diagnostic BUDGET_DECLARATION_CONFLICT; build and deploy fail with BUNDLE_FAILED. Relay also compares cost when it checks a deployment's shared budgets.

  • dbc4e34: domino starts faster. domino --version and --help drop from about 0.4 s to under 0.1 s, and commands that call the management API through the CLI's own transport (domino api, domino builds, domino whoami) from about 0.44 s to about 0.29 s. The CLI now loads the SDK, zod and the system certificate store only when a command needs them. Output, exit statuses and --json results are unchanged.

  • 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.

  • 9e5c5c2: Going live needs a plan where plans apply to the organization. Approving a promotion for a project without a plan starts the organization's one 14-day live trial when it is unused, and otherwise fails with plan-required (403), whose trial says where the trial is. When a trial ends without a plan, live keeps serving for 7 days, then goes offline: participant requests to live fail with live-offline (403) and the live app answers a "This app is offline" page, until the project gets a plan. The error reference describes both codes.

  • a242e19: The errors reference lists ai-allowance-exhausted: a 402 when a project's AI builder balance is empty and the Playground cannot start.

  • ea4814c: Add the trackers reference to the Domino skill, so domino reference trackers serves tracker collection options, record filters and attribution, tracker trigger rules and outcome codes, Discord record fields, and engagement accounting.

  • 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.

    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.

  • Updated dependencies [5c70548]

  • Updated dependencies [65f3a9a]

  • Updated dependencies [8f12977]

  • Updated dependencies [5a8a0a0]

  • Updated dependencies [c91ba35]

  • Updated dependencies [6b946d8]

  • Updated dependencies [3a8a888]

  • Updated dependencies [5ae31db]

  • Updated dependencies [d463895]

  • Updated dependencies [7ff5984]

  • Updated dependencies [6cc08fa]

  • Updated dependencies [921e9d7]

  • Updated dependencies [6156dec]

  • Updated dependencies [a801a1d]

  • Updated dependencies [cbdcdc0]

  • Updated dependencies [823a691]

  • Updated dependencies [cc459ef]

  • Updated dependencies [a4c93bc]

  • Updated dependencies [b8348fd]

  • Updated dependencies [c055c43]

  • Updated dependencies [f89ea5d]

  • Updated dependencies [8a5b7d3]

  • Updated dependencies [c656f61]

  • Updated dependencies [4aaea1a]

    • @domino-sdk/relay@1.55.0

1.54.0

Minor Changes

  • 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.

    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.

  • 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.

    • 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.
    • management.domains.list(), .get(id), .add(hostname), .recheck(id) and .remove(id).
    • Root domains, wildcards, IP addresses and Domino's own domains are refused with invalid-hostname. A project has one custom domain.
    • The hosted-custom-domains capability is now supported.
  • 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.

Patch Changes

  • e735ab8: domino migrate renames id to key in referral bonuses and milestones, tiers, and budget() calls, and balance to ledger in referral bonuses, leaderboards, and budget costs. Strings where v1 takes a handle (quests, qualifiedBy, ledger, prerequisites) get a HANDLE_REFERENCE_REQUIRED error naming the field instead of passing silently, and migrate no longer says "No automatic changes needed" while such errors remain.

  • 50246d7: Hosted app builds no longer set VITE_DOMINO_STAGING. It was true in every build, including the builds the live app serves, so an app that gated test tools on it showed them in live. Builds now see only CI=true. To tell staging from live, call getHostEnvironment() from @domino-sdk/relay/browser in the browser (static and Worker apps), or read DOMINO_ENVIRONMENT in a Worker's server code.

    Staging moved from staging-<project-slug>.domino.run to <project-slug>--staging.domino.page. The old staging addresses, and development previews on domino.run, now answer 410 Gone with a page naming the new address; they do not redirect. Links printed with an old staging address, such as QR codes, need the live address instead.

  • Updated dependencies [ee9e5f1]

  • Updated dependencies [63f7a14]

  • Updated dependencies [50246d7]

  • Updated dependencies [f5bd728]

    • @domino-sdk/relay@1.54.0

1.53.1

Patch Changes

  • 500f752: domino check and domino migrate resolve defineLedger handles, defined in the same file or imported, to their keys, so points(handle, n) no longer warns LEDGER_REFERENCE_DYNAMIC. They also check v1 ledger: references in leaderboards, referral bonuses and budget({ cost: { ledger } }), and count a ledger published in relay.json pointLedgers as { "entry", "exportName" } as declared, so domino migrate no longer proposes an inline duplicate for it. A reference that really needs code to resolve suggests publishing a defineLedger export.
  • @domino-sdk/relay@1.53.1

1.53.0

Not published to npm. Its changes first reached npm in 1.53.1.

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [1b789ce]
    • @domino-sdk/relay@1.53.0

1.52.1

Patch Changes

  • b857dc5: The member imports skill reference sends live imports to Console's Members > Imports, where member imports now open. Console no longer has a separate Member imports page.
  • @domino-sdk/relay@1.52.1

1.52.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [9fadb80]
    • @domino-sdk/relay@1.52.0

1.51.0

Patch Changes

  • Updated dependencies [4dddeb0]
    • @domino-sdk/relay@1.51.0

1.50.0

Minor Changes

  • 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.

    • 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].
    • 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.
    • 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 }.

    Both need publish permission in the test environment, like the other test-member methods; live refuses them.

Patch Changes

  • Updated dependencies [2ecde12]
    • @domino-sdk/relay@1.50.0

1.49.1

Patch Changes

  • 3239667: Tracker trigger outcomes always carry a skip code. A skip recorded before Relay stored codes, whose reason matches no rule, now reads with the code legacy; reading such outcomes no longer fails. TrackerSkipCode is already an open string, so no client change is needed: show legacy by its reason, and retry it if it should count. The CLI's authoring reference names the code.
  • @domino-sdk/relay@1.49.1

1.49.0

Minor Changes

  • 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.

  • 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".

    • 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.
    • A CLI sign-in session can set and delete app secrets in live. Other live writes still need Console or an API key.
    • 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.
    • The hosted-live-app capability is now supported.

Patch Changes

  • Updated dependencies [c262df2]
  • Updated dependencies [41a3fe9]
    • @domino-sdk/relay@1.49.0

1.48.0

Not published to npm. Its changes first reached npm in 1.49.0.

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [3213a87]
    • @domino-sdk/relay@1.48.0

1.47.0

Not published to npm. Its changes first reached npm in 1.49.0.

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [7db52a2]
    • @domino-sdk/relay@1.47.0

1.46.0

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [fc0fc19]
    • @domino-sdk/relay@1.46.0

1.45.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [8bb0a7b]
    • @domino-sdk/relay@1.45.0

1.44.0

Patch Changes

  • 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.
  • 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.
  • Updated dependencies [f114841]
  • Updated dependencies [f114841]
    • @domino-sdk/relay@1.44.0

1.43.0

Minor Changes

  • 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.

    • 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].
    • 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.
    • Tracker record reads mark these records synthetic: true. Collected and ingested records never carry it.
    • 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.

    It needs publish permission in the test environment, like the other test-member methods; live refuses it.

Patch Changes

  • Updated dependencies [64e8df3]
    • @domino-sdk/relay@1.43.0

1.42.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [b7156c7]
    • @domino-sdk/relay@1.42.0

1.41.0

Not published to npm. Its changes first reached npm in 1.42.0.

Patch Changes

  • Updated dependencies [c8e46ee]
    • @domino-sdk/relay@1.41.0

1.40.0

Not published to npm. Its changes first reached npm in 1.42.0.

Minor Changes

  • 68bf9eb: Promote test to live as one reviewed request (ADR 0017). 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.

    • 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.
    • domino promote --json prepares one from test; domino promotions list and domino promotions get read them.
    • 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.

    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.

    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.

Patch Changes

  • Updated dependencies [68bf9eb]
    • @domino-sdk/relay@1.40.0

1.39.1

Patch Changes

  • 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.
  • Updated dependencies [08f24a3]
    • @domino-sdk/relay@1.39.1

1.39.0

Patch Changes

  • Updated dependencies [a9b11a8]
    • @domino-sdk/relay@1.39.0

1.38.1

Patch Changes

  • 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.
  • Updated dependencies [9b02ade]
    • @domino-sdk/relay@1.38.1

1.38.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [167a840]
    • @domino-sdk/relay@1.38.0

1.37.1

Patch Changes

  • 2f18822: Hosted apps moved to domino.page. New staged builds serve at https://<project-slug>--staging.domino.page, and domino dev prints https://<project-slug>--<workspace-hash>.domino.page. domino capabilities describes the new staging address. The CLI shows whatever host the API returns, so no upgrade is needed. Staging links on staging-<project-slug>.domino.run from earlier builds keep working; stage again to get the new address.
  • @domino-sdk/relay@1.37.1

1.37.0

Not published to npm. Its changes first reached npm in 1.37.1.

Minor Changes

  • 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.
  • 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.
  • 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.
  • 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.

Patch Changes

  • Updated dependencies [701a0d8]
  • Updated dependencies [e1b3c70]
  • Updated dependencies [ff024c7]
  • Updated dependencies [ff024c7]
    • @domino-sdk/relay@1.37.0

1.36.0

Patch Changes

  • 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.
  • Updated dependencies [5da9393]
    • @domino-sdk/relay@1.36.0

1.35.0

Not published to npm. Its changes first reached npm in 1.36.0.

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [f7ba8d6]
    • @domino-sdk/relay@1.35.0

1.34.0

Minor Changes

  • 81d0037: Definitions change in test and reach live only through a promotion (ADR 0017). In live, every definition write fails with 422 promotion-required, whatever the credential, Console sessions included. These 1.x management routes refuse live:

    • POST /releases, /releases/batch, /releases/settings, /releases/lifecycle, and /quests/:quest/archive and /unarchive
    • POST /catalog/deploy, /catalog/quests, /catalog/drafts and /catalog/publish
    • POST /engagement/publish and /engagement/change
    • POST /point-ledgers, /point-ledgers/update, /point-ledgers/:ledger/update, /archive and /unarchive
    • POST /calendar and /profile-settings
    • POST /badges, /badges/:badge/update, /archive and /unarchive
    • POST /pickup-inventory and /leaderboards/:board/exclusion-group
    • POST /stock-items, /stock-items/:item/rename, /archive and /unarchive
    • POST /resources/lifecycle, and /archive and /unarchive under /trackers, /referral-programs, /leaderboards, /engagement/rules, /collections, /quest-types and /images

    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.

    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".

Patch Changes

  • Updated dependencies [81d0037]
    • @domino-sdk/relay@1.34.0

1.33.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [3706733]
    • @domino-sdk/relay@1.33.0

1.32.0

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [d6ea67b]
    • @domino-sdk/relay@1.32.0

1.31.0

Minor Changes

  • 07802d2: Reward deliveries, redemptions and deployments page by cursor and nextCursor, so long histories no longer stop silently.

    • 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.
    • 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.
    • 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.

    The deprecated forms keep working through 1.x and are removed in 2.0.0.

Patch Changes

  • 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.
  • Updated dependencies [caf698a]
  • Updated dependencies [07802d2]
    • @domino-sdk/relay@1.31.0

1.30.1

Patch Changes

  • bb09650: The participant API reference's useRelayMutation and server-rendered useRelayQuery examples are now complete components that compile, instead of fragments.
  • @domino-sdk/relay@1.30.1

1.30.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [cd6d78c]
    • @domino-sdk/relay@1.30.0

1.29.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [272a255]
    • @domino-sdk/relay@1.29.0

1.28.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 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.

    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.

    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.

Patch Changes

  • Updated dependencies [932e3c1]
    • @domino-sdk/relay@1.28.0

1.27.1

Not published to npm. Its changes first reached npm in 1.29.0.

Patch Changes

  • 4746d5a: Corrected the agent references: domino capabilities now describes X account linking (OAuth with PKCE, stable X user ID) and profile badge reads, and the participant API reference says apps using their own login can still connect X and Discord accounts and link wallets.
  • @domino-sdk/relay@1.27.1

1.27.0

Not published to npm. Its changes first reached npm in 1.29.0.

Minor Changes

  • 072493d: Leaderboard exclusions are a group (DOM-205):

    • 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.
    • 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.
    • 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.
    • 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.
    • CLI: domino leaderboards exclusion-group BOARD and domino leaderboards bind-exclusion-group BOARD GROUP --reason ….

Patch Changes

  • Updated dependencies [072493d]
    • @domino-sdk/relay@1.27.0

1.26.0

Not published to npm. Its changes first reached npm in 1.29.0.

Patch Changes

  • 91d7ad0: Relay keeps quest submissions (DOM-268). See ADR 0014.

    • 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.
    • 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.
    • 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.
    • 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.
  • Updated dependencies [91d7ad0]

    • @domino-sdk/relay@1.26.0

1.25.1

Not published to npm. Its changes first reached npm in 1.29.0.

Patch Changes

  • 07b7bc1: management.attempts.confirm (POST /management/v1/attempts) and management.passes.verify (POST /management/v1/passes/verify) now need a member that already exists in the project's environment. An unknown member ID is refused with 404 not-found and creates nothing; before, Relay created a member with no sign-in identity, who then collected the quest's rewards. Members who exist but have not joined the project still join it, and test members keep working. The CLI's skill references describe the rule.
  • @domino-sdk/relay@1.25.1

1.25.0

Not published to npm. Its changes first reached npm in 1.29.0.

Patch Changes

  • 55d08ae: Client SDK paper cuts (DOM-160):

    • 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.
    • 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.
    • 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.
    • 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.
    • paginate(read) from @domino-sdk/relay iterates every item of a paged read with for await, following nextCursor.
    • RelayErrorCode lists OperationRetired (operation-retired) and Credential (credential).
    • 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.
    • 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).
  • Updated dependencies [55d08ae]

    • @domino-sdk/relay@1.25.0

1.24.1

Patch Changes

  • 2fd54a5: domino reference now serves every reference the skill ships, including existing-app, hosted, webhooks and errors, so each capability's reference resolves. The references now say the CLI sends a request at most three times in all, and that the 50 app-secret limit applies to each project environment. Capability entries explain why referral milestones and external qualification are not compositions.
  • @domino-sdk/relay@1.24.1

1.24.0

Minor Changes

  • 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>.

    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.

Patch Changes

  • Updated dependencies [f6e030a]
    • @domino-sdk/relay@1.24.0

1.23.2

Patch Changes

  • f049af8: Agent references and capabilities now say that Discord and Telegram trackers keep activity only for accounts a member of the tracker's own project linked, now that members belong to one project.
  • @domino-sdk/relay@1.23.2

1.23.1

Patch Changes

  • 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.
  • Updated dependencies [fd5f211]
    • @domino-sdk/relay@1.23.1

1.23.0

Minor Changes

  • 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.

    • 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.
    • 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.
    • Text settings can carry picker, which Console uses to offer a list.
    • The telegram-membership snippet is a join quest.

Patch Changes

  • Updated dependencies [88c7c00]
    • @domino-sdk/relay@1.23.0

1.22.0

Minor Changes

  • 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.

Patch Changes

  • 07f3a99: Add the campaign-testing agent reference (domino reference campaign-testing): how to simulate a campaign over several days with test members, read the results, fix and replay, and what to watch for, such as odd characters, thresholds, missing provider fields, day boundaries, referral chains and balance gates.
  • Updated dependencies [81beec3]
    • @domino-sdk/relay@1.22.0

1.21.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [5db81c1]
    • @domino-sdk/relay@1.21.0

1.20.0

Minor Changes

  • 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.

    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:

    • relay.auth.providers() returns telegram: { login, miniApp }.
    • relay.auth.telegram has start(mode), account(), disconnect(), and miniApp.signIn(initData) and miniApp.connect(initData). telegramMiniAppInitData() reads the Mini App's launch data.
    • 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.
    • 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.

Patch Changes

  • Updated dependencies [2e1eff4]
    • @domino-sdk/relay@1.20.0

1.19.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [37a57df]
    • @domino-sdk/relay@1.19.0

1.18.1

Patch Changes

  • 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.
  • Updated dependencies [0a6ebf1]
    • @domino-sdk/relay@1.18.1

1.18.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [7000d41]
    • @domino-sdk/relay@1.18.0

1.17.0

Not published to npm. Its changes first reached npm in 1.18.0.

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [d8a5731]
    • @domino-sdk/relay@1.17.0

1.16.0

Minor Changes

  • 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.

    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 for custom tracker ingestion and recovery.

Patch Changes

  • Updated dependencies [b9d767f]
    • @domino-sdk/relay@1.16.0

1.15.1

Not published to npm. Its changes first reached npm in 1.16.0.

Patch Changes

  • 54a725e: Document non-retryable Discord configuration failures, their integration-required attempt code, publication prerequisites, and repair-before-retry guidance in the installed agent references.
  • @domino-sdk/relay@1.15.1

1.15.0

Minor Changes

  • e6849ac: Add typed domino <noun> list|get|… commands for every management primitive, built on the SDK management client: points get|list|award|spend|adjust|set|reverse, point-ledgers list|create|update, trackers list|get|records|summary|content|ingest|outcomes|retry-outcome, leaderboards list|standings|history|exclusions|exclude|include, leaderboard-settlements approve, referral-programs list|qualify, referral-relationships list|history, referral-rewards resolve, offers list, redemptions list|cancel, profiles settings|configure|moderate, calendar get|configure, members get|lookup|oauth-accounts|counters|moderate-counter, groups get|member, quests list|get, releases list|get, attempts list|get, metrics get and pauses list. Each prints a typed result in the CLI contract. Every change except point-ledgers update, which sets a state, is saved before it is sent and takes --operation-id, which becomes the request's actionId, so an uncertain answer resumes with domino operations resume. The existing archive, unarchive, pause, resume, refresh, settlement reject, quest enroll and withdraw, and identity relink commands are saved the same way; their --action-id still sends the change unsaved. The bare status, quests and releases page reads and groups show are deprecated in favor of these commands and are removed in 2.0.0; contract.json marks each deprecated command with a deprecated note. Each capability in domino capabilities now names its commands in cli. See resource commands.

Patch Changes

  • @domino-sdk/relay@1.15.0

1.14.0

Minor Changes

  • 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.

Patch Changes

  • 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.
  • 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.
  • Updated dependencies [0c394de]
  • Updated dependencies [580a641]
  • Updated dependencies [cb252b6]
  • Updated dependencies [3ccb72a]
    • @domino-sdk/relay@1.14.0

1.13.0

Not published to npm. Its changes first reached npm in 1.14.0.

Patch Changes

  • Updated dependencies [c3601dd]
    • @domino-sdk/relay@1.13.0

1.12.0

Not published to npm. Its changes first reached npm in 1.14.0.

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [80f823a]
    • @domino-sdk/relay@1.12.0

1.11.2

Not published to npm. Its changes first reached npm in 1.14.0.

Patch Changes

  • Updated dependencies [7160db6]
    • @domino-sdk/relay@1.11.2

1.11.1

Patch Changes

  • Updated dependencies [90e5141]
    • @domino-sdk/relay@1.11.1

1.11.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [76e7a45]
    • @domino-sdk/relay@1.11.0

1.10.1

Patch Changes

  • Updated dependencies [fa55eff]
    • @domino-sdk/relay@1.10.1

1.10.0

Patch Changes

  • 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.

    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 for the opt-in API.

  • Updated dependencies [f2724f9]

    • @domino-sdk/relay@1.10.0

1.9.0

Patch Changes

  • Updated dependencies [4ae54f9]
    • @domino-sdk/relay@1.9.0

1.8.0

Not published to npm. Its changes first reached npm in 1.9.0.

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [f19da0d]
    • @domino-sdk/relay@1.8.0

1.7.0

Patch Changes

  • Updated dependencies [5662b50]
    • @domino-sdk/relay@1.7.0

1.6.0

Minor Changes

  • 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.

  • 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 reference lists them.

    • RelayErrorCode adds PrerequisitesIncomplete, QuestClosed, QuestPaused, ProfileLocked, InvitationNotFound, InvalidCode, CodeExpired, ChallengeExpired and RateLimited.
    • New managementErrorSchema (@domino-sdk/relay/server) parses the management API's error body, for raw api.raw() answers.

    Messages keep their text, and code stays an open string, so code written against 1.2 keeps working.

    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.

  • 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.

    • 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.
    • domino groups list|show|create|update|archive|unarchive|members|add|remove does the same from the CLI.
    • Groups are archived, never deleted. An archived group's members still count, but cannot change until it is unarchived.
    • 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 }).

    defineGroup and inGroup are unchanged. See groups.

  • 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.

    • 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.
    • 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.
    • 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.
    • New management.trackers.triggerOutcomes(tracker, { ...filters, cursor }) answers { items, nextCursor } and replaces management.trackers.outcomes, whose before and next stay as they were.
    • New management.engagement.ruleVersions(rule, { cursor }) answers { items, nextCursor } and replaces management.engagement.versions({ rule, before }).
    • resourceOptionsSchema (@domino-sdk/relay/schema) adds nextCursor; its cursor field is deprecated.

    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.

  • 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 }).

    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.

  • e3a050e: Management commands on an existing resource now name it in the path, and the client methods take it as their first argument:

    • 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.
    • management.pointLedgers.update(ledger, { name, description }) calls /point-ledgers/:ledger/update and replaces the deprecated pointLedgers.save({ id, name, description }).
    • 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.

    Duplicate and misleading names have clear replacements; the old names stay as deprecated aliases:

    • management.points.record(input) awards, spends, adjusts, reverses or sets a balance, replacing points.update.
    • management.pointLedgers.list() and pointLedgers.create(input) replace points.ledgers() and points.createLedger(input).

    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.

    The routes that took these IDs in the body keep answering released clients. Deprecated forms are removed in 2.0.0.

  • 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.

  • 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.

Patch Changes

  • 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.

  • 8d049d4: The authoring reference now says that ctx.wallet.balance() reads any Cosmos SDK chain management.walletBalanceChains() lists, not only Cosmos Hub and Osmosis. Domino configures these chains and their endpoints; quest code and project settings can't supply an RPC or REST URL. For an unlisted chain or other on-chain state, send records from your own indexer to a custom tracker source. Code changes aren't needed. See token balances.

  • 6f10a51: Engagement rule payouts now earn referral percentage bonuses. Each daily engagement settlement counts as earnings of the rule's qualifying quest, so a bonus in the same ledger that covers that quest, or names no quests, pays the member's inviter in the same commit. Previously these payouts never earned a bonus. Bonuses apply from the next settlement; earlier engagement payouts are not backfilled. The agent referrals reference describes the rule. See percentage bonuses.

  • 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.

    The agent references and capability map now list tracker-restricted ingestion keys (ingestTrackers on management.tokens.create and management.tokens.scope) as supported.

  • 89f742b: Leaderboard prize points no longer earn referral percentage bonuses. Before, a prize award counted as a quest award, so a bonus that named no quests paid the winner's inviter on it, while a bonus with a quests list never did. The points that won the prize already earned their bonuses. Bonuses already paid on earlier prizes stay, and revoking such a prize still corrects them. Quest, engagement and opted-in external awards are unchanged. The agent referrals and authoring references describe the rule. See percentage bonuses.

  • 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.

  • 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.

  • 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).

  • 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.

  • Updated dependencies [4651887]

  • Updated dependencies [75fa01c]

  • Updated dependencies [547600e]

  • Updated dependencies [6c80795]

  • Updated dependencies [6088922]

  • Updated dependencies [6fa9cd5]

  • Updated dependencies [e3a050e]

  • Updated dependencies [b145e16]

  • Updated dependencies [3831c73]

  • Updated dependencies [b3d87d0]

  • Updated dependencies [f322ced]

  • Updated dependencies [5ddcb62]

  • Updated dependencies [f493c48]

  • Updated dependencies [6e9b120]

  • Updated dependencies [0ec4d7d]

  • Updated dependencies [f6b705a]

  • Updated dependencies [65a9765]

    • @domino-sdk/relay@1.6.0

1.5.2

Patch Changes

  • 68a30f4: domino dev keeps the app running when the process pauses for longer than its 90-second preview lease, as when a laptop sleeps or a playground VM is paused. On resume it claims the preview again and reconnects the tunnel without restarting the dev server, so the preview URL and port stay the same. A dropped lease renewal or a tunnel connector that exits now retries with backoff instead of stopping the app. domino dev still stops when another run has taken over the checkout's preview, when Domino refuses the preview (for example after publish permission is revoked), or when the API stays unreachable for about two and a half minutes.
  • @domino-sdk/relay@1.5.2

1.5.1

Patch Changes

  • 9fe0ec9: Point the moved-project message, the app-secrets capability, the bundled agent references and the starters' "no sign-in method" notice at Console's new Settings pages: API keys, App login keys, App secrets, Sign-in, Profiles and Staging access.
  • @domino-sdk/relay@1.5.1

1.5.0

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [625dcbc]
    • @domino-sdk/relay@1.5.0

1.4.0

Patch Changes

  • Updated dependencies [da46e7b]
    • @domino-sdk/relay@1.4.0

1.3.0

Patch Changes

  • Updated dependencies [86114a5]
    • @domino-sdk/relay@1.3.0

1.2.0

Minor Changes

  • 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.

    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.

  • 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.

Patch Changes

  • Updated dependencies [7f69653]
  • Updated dependencies [cdea4a0]
    • @domino-sdk/relay@1.2.0

1.1.1

Patch Changes

  • 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.
  • Updated dependencies [1d2a0cc]
    • @domino-sdk/relay@1.1.1

1.1.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [ead35b5]
    • @domino-sdk/relay@1.1.0

1.0.0

Major Changes

  • 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.

    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.

  • 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.

    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 in the v1 upgrade guide.

  • 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.

  • fa16503: The CLI's automation contract is frozen for v1. Every failure has a stable error code, and the exit status says what kind of failure it was: 0 success, 1 failure, 2 usage error (was 1), 3 a change that may have taken effect without confirmation (was 1), and 130 cancelled by SIGINT or a prompt, 143 by SIGTERM (was 1). With --json, the error on stderr's last line always has error, code and outcome, plus status, apiCode, details or operation when they apply; Commander's commander.* codes are gone. An uncertain deploy publication or stage build reports an operation whose resume arguments pin the full scope and finish it from any directory, announced as a JSON operation event on stderr right before the request is sent. deploy --resume and stage --request-id now require that scope as flags and need no checkout. doctor, check and migrate also write their failure to stderr. The undocumented domino GET /path spelling is removed; use domino api GET /path. domino help with an unknown command fails with UNKNOWN_COMMAND. domino dev is now listed under Experimental commands. Result and error JSON Schemas, fixtures, a command inventory and cli/contract/index.mjs (the zod schemas by name) ship in cli/contract/, and domino reference automation explains them. To upgrade, see CLI errors have stable codes and exit statuses and Call the management API with domino api.

  • 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.

    • 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.
    • Date.now(), new Date() and Date() return ctx.now and do not advance, even across awaited capability calls. performance.now() returns 0.
    • Math.random(), crypto.getRandomValues() and crypto.randomUUID() repeat the same sequence for one attempt and differ between attempts.

    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 in the v1 upgrade guide.

  • 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.

    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.

    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.

    To upgrade, see Observation triggers become tracker triggers and The observe permission is replaced by enroll.

  • 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.

    See Engagement budgets are a list of periods in the v1 upgrade guide.

  • 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.

  • 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.

    Upgrade steps: Content stages.

  • 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.

    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.

    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.

    Published tracker definitions in status, list, record-page, summary, and deployment responses use trackerDefinitionResponseSchema and TrackerDefinitionResponse. Authoring keeps the strict trackerDefinitionSchema and TrackerDefinition.

    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.

    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().

  • 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.

  • 54fcf41: The participant client follows one convention for paging, sessions and errors.

    • 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.
    • 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).
    • 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.
    • 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).
    • 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.
    • Hosted proxies no longer forward the 0.x single-wallet routes (/v1/me/wallet, /challenge, /verify).

    Upgrade: One paging, session and error convention in the participant client in the SDK v1 upgrade guide maps every method and has search commands.

  • 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 in the v1 upgrade guide.

  • 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.

    • 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.
    • streak() and calendar leaderboard windows no longer take a timeZone; both throw if given one. Streaks and boards already published with one still read.
    • Budgets reset at midnight in the project timezone instead of UTC, and accept period: "month".
    • 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.

    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 in the v1 upgrade guide.

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

  • 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.

  • 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 before replacing a 0.x installation.

    Stable APIs follow semantic versioning throughout the supported v1 lifetime. Compatibility exemptions apply only to explicitly experimental entrypoints.

  • ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 2 is the first stable contract, and all pre-v1 bundles must be rebuilt and republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See Wallet reads return every linked wallet in the v1 upgrade guide.

  • 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.

    • 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.
    • 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.
    • 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.
    • 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.

    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.

Minor Changes

  • 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.

    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.

    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.

    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.

  • 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. Update custom attempt renderers using the counter upgrade steps.

  • e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose management.webhooks, verifyWebhook, shared activity constants, and domino webhooks commands. This is additive; existing integrations need no changes. See Receive outbound webhooks for an optional polling migration.

  • 903ffad: Persist recoverable CLI mutations before sending and add operations list, show and resume. Creation resumes through repository and checkout failures; quest lifecycle changes retain their action ID and scoped input. Errors for known partial failures may now include an operation to resume. See the saved CLI mutation recovery upgrade guide.

  • 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.

    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.

  • d1ef769: Relay answers 503 only when it provably did nothing, so a write it reports as failed with 503 is safe to send again under a new action ID:

    • An unexpected server error is 500, not 503. It can follow a commit, and the SDK, the management client and the CLI already treat 500 on a write as uncertain.
    • A lost or unusable answer from an internal service is 502, or 504 when it timed out, where it was 503: publication, the Discord integration service, the preview service, Clerk API key changes, and the cloud builder ("Could not start cloud build. Retry with the same request ID." is now 502 or 504).
    • 503 stays for refusals before any work: missing configuration, a dependency check that fails before the transaction, and a data space closed for maintenance, which now sends Retry-After: 60.
    • Rate-limited answers (429) send Retry-After with the seconds until the limit's window resets: sign-in starts, email codes, wallet sign-in and wallet linking, CLI device login and approval, identity exchange, profile and avatar changes, playground starts, preview starts, development keys, tracker previews and feedback.

    The participant-api and hosted agent references describe the 503 rule and the 502 or 504 build start.

    Upgrade custom retry handling using Treat failed writes as uncertain when Relay cannot prove refusal. Evidence and avatar PUTs, email delivery, Clerk cleanup, and activation failures after a commit now preserve uncertainty. Staging password limits also carry Retry-After.

  • 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.

  • 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.

    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.

  • 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.

  • 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.

    See Performance evaluators no longer select rewards in the upgrade guide.

  • 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.

    See Prerequisites are typed eligibility rules and Authoring references use typed handles in the upgrade guide.

  • 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 before upgrading.

    Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.

    Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.

  • 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.

  • 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the resource lifecycle.

  • ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.

    • 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.
    • 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.
    • 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.
    • A session-bound participant call that reads the current session first shares one deadline with that read, and auth.session() honours timeout.
    • 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.
    • 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.
    • 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.

    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 for the recovery-handler change.

    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 for the recovery-handler change.

  • 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.

    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 for old and new code, capture semantics, and search patterns.

  • 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.

  • 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.

    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.

  • 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.

  • 0052894: The management client has typed methods for the routes Console and scripts called by path: attempts.confirm, attempts.review and attempts.retry, passes.verify, handover.begin and handover.confirm, operations.configure, inventory.addStock, quests.updateSettings, tokens, grants, team and identityIntegrations. The new members.lookup({ provider, subject }) (GET /management/v1/members/lookup, read permission) finds the one member of the project holding exactly that account, and fails with 404 member-not-found or 409 account-ambiguous.

    Breaking: every failed call rejects with a ManagementError whose kind (http, network, timeout, aborted, invalid-response, client) separates a server refusal from a lost or unreadable answer and from input rejected before sending. It keeps the server's message and any code, including codes this version does not know, and reports uncertain, with operationId (the actionId it sent) and recovery, when a write may have been applied. It extends the SDK's shared RequestError; isManagementError() recognizes it. CreationError is removed. Calls end after 30 seconds by default; every method takes { signal, timeout } as its last argument, and the client takes a default timeout. To upgrade, see Catch ManagementError instead of CreationError or a status property.

    Breaking: member, people, unlinkMemberWallet and setMemberWalletSignIn move to members.get, members.people, members.unlinkWallet and members.setWalletSignIn. See Member methods moved under management.members.

  • 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.

    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.

    See Custom sources declare their record data and Quest settings come only from settings builders in the upgrade guide.

Patch Changes

  • 1019218: Console Audit history now records every point ledger created in Console or with management.points.createLedger, as Point ledger created, with the ledger's key and name, the environment and who created it. Before, only edits were audited.

  • 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.

  • 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.

  • 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.

  • bd960a4: Only a person signed in to Console can create or edit a point ledger in live. management.points.createLedger and management.pointLedgers.save with an API key or a CLI sign-in now get 403 "Approve live publication in the console" in live, as publication already did; in test they work as before. To upgrade, see test and live. Live operator controls stay open to a personal API key with publish: operation pauses (POST /management/v1/operations), management.referrals.control, management.leaderboards.exclude and group, and management.trackers.control. Console Audit history now lists leaderboard exclusion, group and tracker control changes with the key's owner as the actor. The agent references and capabilities.json describe both rules.

  • 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.

  • 2f78766: Fix domino check and domino migrate when a valid manifest omits either discovery array. Omitted arrays use the same empty defaults as domino build, and explicit resources remain included.

  • 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.

  • 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.

    The classic starter's header now reads the balance with useBalances() instead of its /api/me/points server route, which is gone.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

  • 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.

    • Zod schemas (and helpers such as identifier, caip10 and xPostId) come from @domino-sdk/relay/schema.
    • 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.
    • avatarSrc, defaultAvatar, defaultAvatarSvg and walletAvatarSeed come from @domino-sdk/relay/avatar, and no longer from @domino-sdk/relay or @domino-sdk/relay/react.
    • 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.
    • No longer exported: questActions and reconcileQuestDraft. Open a quest controller with relay.quests.open(view); its quest-updated result already carries the reconciled draft.
    • The package declares sideEffects: false.
    • 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.

    Upgrade: Runtime entry exports only the client in the SDK v1 upgrade guide has the import mapping and search commands.

  • e635fe2: Troubleshooting reference: when a build or typecheck fails on an API that no longer exists after an upgrade, read the Upgrade to Relay SDK v1 guide.

  • Updated dependencies [ad91102]

  • Updated dependencies [4b10080]

  • Updated dependencies [4205914]

  • Updated dependencies [6d66f96]

  • Updated dependencies [afe1128]

  • Updated dependencies [ed4988c]

  • Updated dependencies [b0bffa0]

  • Updated dependencies [bac9ca4]

  • Updated dependencies [c320586]

  • Updated dependencies [a803b30]

  • Updated dependencies [e1cba88]

  • Updated dependencies [1141af4]

  • Updated dependencies [e7f81ff]

  • Updated dependencies [23c85ba]

  • Updated dependencies [8465d40]

  • Updated dependencies [84feaf7]

  • Updated dependencies [6cd0ba7]

  • Updated dependencies [3a38259]

  • Updated dependencies [83c4426]

  • Updated dependencies [71cf912]

  • Updated dependencies [f2f1b75]

  • Updated dependencies [c320586]

  • Updated dependencies [4626004]

  • Updated dependencies [2149973]

  • Updated dependencies [8c06912]

  • Updated dependencies [6c30583]

  • Updated dependencies [54fcf41]

  • Updated dependencies [710e520]

  • Updated dependencies [6c30583]

  • Updated dependencies [c320586]

  • Updated dependencies [c320586]

  • Updated dependencies [c832384]

  • Updated dependencies [23c85ba]

  • Updated dependencies [2528cec]

  • Updated dependencies [569989e]

  • Updated dependencies [8a8870e]

  • Updated dependencies [11ab1d8]

  • Updated dependencies [7e37049]

  • Updated dependencies [c919948]

  • Updated dependencies [c2390b3]

  • Updated dependencies [909f343]

  • Updated dependencies [ddfc526]

  • Updated dependencies [8b706af]

  • Updated dependencies [89ea6ba]

  • Updated dependencies [67d0d8c]

  • Updated dependencies [d3b6261]

  • Updated dependencies [1084288]

  • Updated dependencies [5d4ec3e]

  • Updated dependencies [d7f9fe4]

  • Updated dependencies [ae047ca]

  • Updated dependencies [0a89168]

  • Updated dependencies [e1e701b]

  • Updated dependencies [cf5b02c]

  • Updated dependencies [1e23d8f]

  • Updated dependencies [bdb89a4]

    • @domino-sdk/relay@1.0.0

1.0.0-next.34

Major Changes

  • 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.

    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.

  • 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.

    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 in the v1 upgrade guide.

  • 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.

  • 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.

  • 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.

    Upgrade steps: Content stages.

  • 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 in the v1 upgrade guide.

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

  • ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 1 remains supported, while protocol-less bundles must be republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See Wallet reads return every linked wallet in the v1 upgrade guide.

Minor Changes

  • 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. Update custom attempt renderers using the counter upgrade steps.
  • 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.
  • 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.
  • 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the resource lifecycle.
  • 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.

Patch Changes

  • 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.

  • 2f78766: Fix domino check and domino migrate when a valid manifest omits either discovery array. Omitted arrays use the same empty defaults as domino build, and explicit resources remain included.

  • 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.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

  • Updated dependencies [ad91102]

  • Updated dependencies [4b10080]

  • Updated dependencies [4205914]

  • Updated dependencies [6d66f96]

  • Updated dependencies [ed4988c]

  • Updated dependencies [b0bffa0]

  • Updated dependencies [bac9ca4]

  • Updated dependencies [84feaf7]

  • Updated dependencies [6cd0ba7]

  • Updated dependencies [3a38259]

  • Updated dependencies [71cf912]

  • Updated dependencies [6c30583]

  • Updated dependencies [6c30583]

  • Updated dependencies [c832384]

  • Updated dependencies [2528cec]

  • Updated dependencies [8a8870e]

  • Updated dependencies [11ab1d8]

  • Updated dependencies [ae047ca]

  • Updated dependencies [0a89168]

  • Updated dependencies [e1e701b]

  • Updated dependencies [1e23d8f]

    • @domino-sdk/relay@1.0.0-next.34

1.0.0-next.33

Major Changes

  • 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.

Patch Changes

  • Updated dependencies [89ea6ba]
    • @domino-sdk/relay@1.0.0-next.33

1.0.0-next.32

Patch Changes

  • 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.

  • 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.

    Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.

  • Updated dependencies [6c30583]

  • Updated dependencies [6c30583]

    • @domino-sdk/relay@1.0.0-next.32

1.0.0-next.31

Minor Changes

  • 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 before upgrading.

    Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.

    Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.

Patch Changes

  • Updated dependencies [569989e]
    • @domino-sdk/relay@1.0.0-next.31

1.0.0-next.30

Minor Changes

  • 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.

    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.

    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.

    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.

Patch Changes

  • Updated dependencies [afe1128]
    • @domino-sdk/relay@1.0.0-next.30

1.0.0-next.29

Minor Changes

  • 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.

    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 for old and new code, capture semantics, and search patterns. This ships in the 1.0.0-next prerelease series.

  • 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.

Patch Changes

  • Updated dependencies [67d0d8c]
  • Updated dependencies [d3b6261]
    • @domino-sdk/relay@1.0.0-next.29

1.0.0-next.28

Patch Changes

  • 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.
  • Updated dependencies [83c4426]
    • @domino-sdk/relay@1.0.0-next.28

1.0.0-next.27

Minor Changes

  • e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose management.webhooks, verifyWebhook, shared activity constants, and domino webhooks commands. This is additive; existing integrations need no changes. See Receive outbound webhooks for an optional polling migration.

Patch Changes

  • Updated dependencies [e7f81ff]
    • @domino-sdk/relay@1.0.0-next.27

1.0.0-next.26

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [8c06912]
    • @domino-sdk/relay@1.0.0-next.26

1.0.0-next.25

Minor Changes

  • 903ffad: Persist recoverable CLI mutations before sending and add operations list, show and resume. Creation resumes through repository and checkout failures; quest lifecycle changes retain their action ID and scoped input. Errors for known partial failures may now include an operation to resume. See the saved CLI mutation recovery upgrade guide.

Patch Changes

  • @domino-sdk/relay@1.0.0-next.25

1.0.0-next.24

Major Changes

  • 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.

    • 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".
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

    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.

Patch Changes

  • Updated dependencies [2528cec]
    • @domino-sdk/relay@1.0.0-next.24

1.0.0-next.23

Major Changes

  • 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.

Patch Changes

  • Updated dependencies [2149973]
    • @domino-sdk/relay@1.0.0-next.23

1.0.0-next.22

Major Changes

  • 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.

    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.

    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.

    Published tracker definitions in status, list, record-page, summary, and deployment responses use trackerDefinitionResponseSchema and TrackerDefinitionResponse. Authoring keeps the strict trackerDefinitionSchema and TrackerDefinition.

    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.

    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().

Patch Changes

  • Updated dependencies [4626004]
    • @domino-sdk/relay@1.0.0-next.22

1.0.0-next.21

Minor Changes

  • ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.

    • 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.
    • 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.
    • 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.
    • A session-bound participant call that reads the current session first shares one deadline with that read, and auth.session() honours timeout.
    • 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.
    • 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.
    • 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.

    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 for the recovery-handler change.

    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 for the recovery-handler change.

Patch Changes

  • Updated dependencies [ddfc526]
    • @domino-sdk/relay@1.0.0-next.21

1.0.0-next.20

Minor Changes

  • d1ef769: Relay answers 503 only when it provably did nothing, so a write it reports as failed with 503 is safe to send again under a new action ID:

    • An unexpected server error is 500, not 503. It can follow a commit, and the SDK, the management client and the CLI already treat 500 on a write as uncertain.
    • A lost or unusable answer from an internal service is 502, or 504 when it timed out, where it was 503: publication, the Discord integration service, the preview service, Clerk API key changes, and the cloud builder ("Could not start cloud build. Retry with the same request ID." is now 502 or 504).
    • 503 stays for refusals before any work: missing configuration, a dependency check that fails before the transaction, and a data space closed for maintenance, which now sends Retry-After: 60.
    • Rate-limited answers (429) send Retry-After with the seconds until the limit's window resets: sign-in starts, email codes, wallet sign-in and wallet linking, CLI device login and approval, identity exchange, profile and avatar changes, playground starts, preview starts, development keys, tracker previews and feedback.

    The participant-api and hosted agent references describe the 503 rule and the 502 or 504 build start.

    Upgrade custom retry handling using Treat failed writes as uncertain when Relay cannot prove refusal. Evidence and avatar PUTs, email delivery, Clerk cleanup, and activation failures after a commit now preserve uncertainty. Staging password limits also carry Retry-After.

Patch Changes

  • @domino-sdk/relay@1.0.0-next.20

1.0.0-next.19

Patch Changes

  • Updated dependencies [1141af4]
    • @domino-sdk/relay@1.0.0-next.19

1.0.0-next.18

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [8465d40]
    • @domino-sdk/relay@1.0.0-next.18

1.0.0-next.17

Patch Changes

  • Updated dependencies [c919948]
    • @domino-sdk/relay@1.0.0-next.17

1.0.0-next.16

Major Changes

  • 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.

    • 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.
    • 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.
    • 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.
    • 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.

    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.

Patch Changes

  • Updated dependencies [bdb89a4]
    • @domino-sdk/relay@1.0.0-next.16

1.0.0-next.15

Major Changes

  • 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.

    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.

    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.

    To upgrade, see Observation triggers become tracker triggers and The observe permission is replaced by enroll.

Patch Changes

  • Updated dependencies [e1cba88]
    • @domino-sdk/relay@1.0.0-next.15

1.0.0-next.14

Patch Changes

  • Updated dependencies [c2390b3]
    • @domino-sdk/relay@1.0.0-next.14

1.0.0-next.13

Major Changes

  • 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.

    See Engagement budgets are a list of periods in the v1 upgrade guide.

  • 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.

    • 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.
    • streak() and calendar leaderboard windows no longer take a timeZone; both throw if given one. Streaks and boards already published with one still read.
    • Budgets reset at midnight in the project timezone instead of UTC, and accept period: "month".
    • 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.

    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 in the v1 upgrade guide.

Patch Changes

  • Updated dependencies [23c85ba]
  • Updated dependencies [23c85ba]
    • @domino-sdk/relay@1.0.0-next.13

1.0.0-next.12

Patch Changes

  • 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.

    The classic starter's header now reads the balance with useBalances() instead of its /api/me/points server route, which is gone.

  • Updated dependencies [710e520]

    • @domino-sdk/relay@1.0.0-next.12

1.0.0-next.11

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [f2f1b75]
  • Updated dependencies [909f343]
    • @domino-sdk/relay@1.0.0-next.11

1.0.0-next.10

Minor Changes

  • 0052894: The management client has typed methods for the routes Console and scripts called by path: attempts.confirm, attempts.review and attempts.retry, passes.verify, handover.begin and handover.confirm, operations.configure, inventory.addStock, quests.updateSettings, tokens, grants, team and identityIntegrations. The new members.lookup({ provider, subject }) (GET /management/v1/members/lookup, read permission) finds the one member of the project holding exactly that account, and fails with 404 member-not-found or 409 account-ambiguous.

    Breaking: every failed call rejects with a ManagementError whose kind (http, network, timeout, aborted, invalid-response, client) separates a server refusal from a lost or unreadable answer and from input rejected before sending. It keeps the server's message and any code, including codes this version does not know, and reports uncertain, with operationId (the actionId it sent) and recovery, when a write may have been applied. It extends the SDK's shared RequestError; isManagementError() recognizes it. CreationError is removed. Calls end after 30 seconds by default; every method takes { signal, timeout } as its last argument, and the client takes a default timeout. To upgrade, see Catch ManagementError instead of CreationError or a status property.

    Breaking: member, people, unlinkMemberWallet and setMemberWalletSignIn move to members.get, members.people, members.unlinkWallet and members.setWalletSignIn. See Member methods moved under management.members.

Patch Changes

  • @domino-sdk/relay@1.0.0-next.10

1.0.0-next.9

Minor Changes

  • 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.

    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.

    See Custom sources declare their record data and Quest settings come only from settings builders in the upgrade guide.

Patch Changes

  • Updated dependencies [cf5b02c]
    • @domino-sdk/relay@1.0.0-next.9

1.0.0-next.8

Major Changes

  • 54fcf41: The participant client follows one convention for paging, sessions and errors.

    • 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.
    • 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).
    • 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.
    • 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).
    • 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.
    • Hosted proxies no longer forward the 0.x single-wallet routes (/v1/me/wallet, /challenge, /verify).

    Upgrade: One paging, session and error convention in the participant client in the SDK v1 upgrade guide maps every method and has search commands.

Patch Changes

  • Updated dependencies [54fcf41]
    • @domino-sdk/relay@1.0.0-next.8

1.0.0-next.7

Patch Changes

  • 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.

    • Zod schemas (and helpers such as identifier, caip10 and xPostId) come from @domino-sdk/relay/schema.
    • 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.
    • avatarSrc, defaultAvatar, defaultAvatarSvg and walletAvatarSeed come from @domino-sdk/relay/avatar, and no longer from @domino-sdk/relay or @domino-sdk/relay/react.
    • 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.
    • No longer exported: questActions and reconcileQuestDraft. Open a quest controller with relay.quests.open(view); its quest-updated result already carries the reconciled draft.
    • The package declares sideEffects: false.
    • 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.

    Upgrade: Runtime entry exports only the client in the SDK v1 upgrade guide has the import mapping and search commands.

  • Updated dependencies [1084288]

    • @domino-sdk/relay@1.0.0-next.7

1.0.0-next.6

Patch Changes

  • 1019218: Console Audit history now records every point ledger created in Console or with management.points.createLedger, as Point ledger created, with the ledger's key and name, the environment and who created it. Before, only edits were audited.
  • Updated dependencies [8b706af]
    • @domino-sdk/relay@1.0.0-next.6

1.0.0-next.5

Major Changes

  • fa16503: The CLI's automation contract is frozen for v1. Every failure has a stable error code, and the exit status says what kind of failure it was: 0 success, 1 failure, 2 usage error (was 1), 3 a change that may have taken effect without confirmation (was 1), and 130 cancelled by SIGINT or a prompt, 143 by SIGTERM (was 1). With --json, the error on stderr's last line always has error, code and outcome, plus status, apiCode, details or operation when they apply; Commander's commander.* codes are gone. An uncertain deploy publication or stage build reports an operation whose resume arguments pin the full scope and finish it from any directory, announced as a JSON operation event on stderr right before the request is sent. deploy --resume and stage --request-id now require that scope as flags and need no checkout. doctor, check and migrate also write their failure to stderr. The undocumented domino GET /path spelling is removed; use domino api GET /path. domino help with an unknown command fails with UNKNOWN_COMMAND. domino dev is now listed under Experimental commands. Result and error JSON Schemas, fixtures, a command inventory and cli/contract/index.mjs (the zod schemas by name) ship in cli/contract/, and domino reference automation explains them. To upgrade, see CLI errors have stable codes and exit statuses and Call the management API with domino api.

Patch Changes

  • @domino-sdk/relay@1.0.0-next.5

1.0.0-next.4

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [d7f9fe4]
    • @domino-sdk/relay@1.0.0-next.4

1.0.0-next.3

Major Changes

  • 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.

    • 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.
    • Date.now(), new Date() and Date() return ctx.now and do not advance, even across awaited capability calls. performance.now() returns 0.
    • Math.random(), crypto.getRandomValues() and crypto.randomUUID() repeat the same sequence for one attempt and differ between attempts.

    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 in the v1 upgrade guide.

Patch Changes

  • bd960a4: Only a person signed in to Console can create or edit a point ledger in live. management.points.createLedger and management.pointLedgers.save with an API key or a CLI sign-in now get 403 "Approve live publication in the console" in live, as publication already did; in test they work as before. To upgrade, see test and live. Live operator controls stay open to a personal API key with publish: operation pauses (POST /management/v1/operations), management.referrals.control, management.leaderboards.exclude and group, and management.trackers.control. Console Audit history now lists leaderboard exclusion, group and tracker control changes with the key's owner as the actor. The agent references and capabilities.json describe both rules.
  • Updated dependencies [a803b30]
  • Updated dependencies [7e37049]
    • @domino-sdk/relay@1.0.0-next.3

1.0.0-next.2

Minor Changes

  • 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.

    See Performance evaluators no longer select rewards in the upgrade guide.

  • 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).

    See Prerequisites are typed eligibility rules in the upgrade guide.

Patch Changes

  • 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.
  • 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.
  • Updated dependencies [c320586]
  • Updated dependencies [c320586]
  • Updated dependencies [c320586]
  • Updated dependencies [c320586]
    • @domino-sdk/relay@1.0.0-next.2

1.0.0-next.1

Patch Changes

  • e635fe2: Troubleshooting reference: when a build or typecheck fails on an API that no longer exists after an upgrade, read the Upgrade to Relay SDK v1 guide.
  • @domino-sdk/relay@1.0.0-next.1

1.0.0-next.0

Major Changes

  • 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.

    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.

    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.

Patch Changes

  • Updated dependencies [5d4ec3e]
    • @domino-sdk/relay@1.0.0-next.0

0.31.1

Patch Changes

  • f76abb2: Update the agent references and capability descriptions for verified identities, account-reply and kudos tracker quests, and ranking an external score with a dedicated point ledger.
  • @domino-sdk/relay@0.31.1

0.31.0

Minor Changes

  • 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.

    • 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.
    • 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.
    • 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.
    • 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".
    • 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.

    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:

    • 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.
    • 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.
    • 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.
    • Trigger outcomes always carry code: trackers.outcomes needs this SDK.

    Breaking changes. For code written against the tracker triggers of 0.25.0 to 0.27.0:

    • Replace customSource(namespace, { authors: "wallet" }) with customSource(namespace, { identities: ["wallet"] }). authors stays readable on published trackers and is deprecated.
    • Replace automatic({ tracker, beneficiary: "replied-to" }) with beneficiary: "inReplyTo".
    • TrackerBeneficiary and trackerBeneficiarySchema are removed; the beneficiary is a role string, or "replied-to" on releases published before roles.

    Upgrading. Deploy the backend (migration 0074_tracker_trigger_primitives) before publishing quests that use the new options; older servers refuse them.

Patch Changes

  • Updated dependencies [3d0bee0]
    • @domino-sdk/relay@0.31.0

0.30.0

Minor Changes

  • 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.

    • 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.
    • 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:: it is not a supported EVM chain. …".
    • 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.

    Breaking. evmChains, evmChainSchema, EvmChain and EvmChainInfo are no longer exported from @domino-sdk/relay.

    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.

  • c6afc57: ingest is a normal management permission: it sends records to custom trackers (management.trackers.ingest) and allows nothing else.

    • Editors hold ingest, and organization administrators hold every permission, so management.access() lists it for them. rolePermissions.editor includes it.
    • 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.
    • Hosted-app keys (dhk_) now list ingest in the minted key, the development key and access(), alongside read and points. They always held it.
    • 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").
    • domino dev needs your read, points and ingest permissions for its development key; editors have all three.
    • tokenSchema (the personal API key list) drops permissions this release does not know instead of rejecting the list, as access() already does.

    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.

Patch Changes

  • Updated dependencies [c6afc57]
  • Updated dependencies [c6afc57]
    • @domino-sdk/relay@0.30.0

0.29.0

Minor Changes

  • 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.

    • relay.profiles.get(id) returns the profile only: id, name and avatar (profileSummarySchema).
    • 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.
    • 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.
    • 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.
    • React: useLeaderboardPlacement(board, id), useProfileCompletions(id, { limit }) with loadMore(), and useProfileReferral(id, program). useProfile(id) returns the profile only.
    • Profile settings: defaults is an ordered list of the accounts that fill in names and pictures, "x" and "discord" ([] for generated). profileDefaultProviderSchema lists them.
    • 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.
    • proxyParticipant and the API's scope selector admit the new routes (isProfileParticipantRoute).
    • 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.

    Upgrading. Breaking for apps, servers and CLIs on 0.25 to 0.27 that use profiles:

    • 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.
    • 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.
    • 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.
    • 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 [].
    • 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.
    • 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.
    • 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.

Patch Changes

  • Updated dependencies [28f8bcf]
    • @domino-sdk/relay@0.29.0

0.28.0

Minor Changes

  • 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.

    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.

    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.

    Management member accounts now carry kind: wallet for wallet identities and account for the rest. Responses from older servers read as account.

Patch Changes

  • Updated dependencies [30dda1d]
    • @domino-sdk/relay@0.28.0

0.27.0

Minor Changes

  • 9ef2629: Cosmos wallets work on any Cosmos SDK chain your app chooses. Relay no longer ships a list of Cosmos chains.

    • 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.
    • 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.
    • 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.
    • React: useWalletDiscovery({ cosmos }) and useSignInWallets(families, { cosmos }) take the app's chains (one or an array).
    • 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.
    • 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.

    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.

    • 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.

    • 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.

    • 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:

      import type { CosmosChain, WalletSignInNetwork } from "@domino-sdk/relay"
      
      /** The Cosmos SDK chains this app offers wallet sign-in on. */
      export const COSMOS_CHAINS: CosmosChain[] = [{ chainId: "cosmoshub-4" }]
      
      const NETWORK_NAMES: Record<WalletSignInNetwork, string> = {
        solana: "Solana",
        eip155: "EVM",
        cosmos: "Cosmos",
      }
      
      // In the sign-in component:
      const wallets = useSignInWallets(methods?.wallets, {
        cosmos: COSMOS_CHAINS,
      })
    • cosmosNetworks is removed, and CosmosNetwork is any cosmos:<chain ID>. Pass your chain to the adapters instead of looking it up.

    • useWalletDiscovery() no longer lists Cosmos wallets on a default chain: pass { cosmos: { chainId: "…" } }. useSignInWallets(networks) lists Cosmos wallets only with { cosmos }.

    • WalletSignInNetwork is now "solana" | "eip155" | "cosmos"; a SignInWallet's network is its family, and a Cosmos wallet's chain is signer.network.

Patch Changes

  • Updated dependencies [9ef2629]
    • @domino-sdk/relay@0.27.0

0.26.0

Minor Changes

  • 81693c8: Archive and unarchive quests published from code. Deleting a quest file never archived the published quest, and there was no way to do it.

    • CLI: domino quests archive <key> --reason "…" and domino quests unarchive <key> --reason "…" (optional --expected-release <id>, --json). Test environment only; live changes need Console.
    • Management: quests.archive(quest, { reason, expectedRelease?, actionId? }) and quests.unarchive(...) call POST /management/v1/releases/lifecycle with publish permission, returning { quest, key, title, release, lifecycle, changed }. New questLifecycleInputSchema and questLifecycleSchema.
    • An archived quest admits no new attempts, leaves collections and no longer counts toward supportedInteractions; completions, attempts and rewards stay. Deploying its code again keeps it archived. Unarchiving re-checks the app's supported interactions and the prerequisite graph, and leaves the quest paused while a resource setting is empty.
    • domino deploy release summaries now include each release's lifecycle.

    Upgrading. Deploy the backend first; older servers answer 404 for the new route. No breaking changes.

Patch Changes

  • @domino-sdk/relay@0.26.0

0.25.1

Patch Changes

  • 7420088: Hosted builds with many quests no longer fail with "Build artifact exceeds 20 MB". Quest and catalog code from .domino-build is now compressed and staged beside the build artifact. Its limits are 64 MB of JSON and 10 MB compressed, and the dependencies each quest bundles compress well when they repeat. The 20 MB limit now covers only Worker modules and public assets.

    • Contract: buildArtifactsSchema accepts format: 2 artifacts, which carry a deployment reference (stagedDeploymentSchema) and the catalog's images in place of inline quests and catalog. Format 1 artifacts still parse. stagedDeploymentContentSchema, stagedDeploymentLimits and stagedDeploymentKey() describe the staged object.

    No changes are needed in apps. No breaking changes.

  • @domino-sdk/relay@0.25.1

0.25.0

Minor Changes

  • 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.

    • 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.
    • React: useMyProfile() and useProfile(id); avatarSrc(avatar) renders any profile avatar. Generated avatars use the same seed as relay.me.get().avatarSeed.
    • 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).
    • 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.

    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.

  • 531f41e: Members can link several Solana, EVM and Provenance wallets to a project and unlink them.

    • relay.wallets.list(), link(signer), unlink(wallet), and the lower-level challenge() and verify().
    • Dependency-free signer adapters and discovery in @domino-sdk/relay/browser: evmSigner, solanaSigner, walletStandardSolanaSigner, cosmosSigner, discoverEvmWallets, discoverSolanaWallets, discoverCosmosWallets.
    • React: useWallets() and useWalletDiscovery().
    • Evaluators: ctx.wallet.list() returns the member's linked wallets, pinned once per attempt.
    • Management: unlinkMemberWallet(member, { chain, address, reason }) (requires review), and wallets in the member detail.
    • Contract helpers: caip10(), cosmosNetworks, isWalletParticipantRoute(), and the LinkedWallet, WalletSigner and related schemas.

    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.

  • c71712f: Quests can trigger from custom tracker records attributed to members' verified wallets.

    • 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.
    • 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.
    • management.trackers.outcomes(tracker, query) explains each admission or skip with its reason; management.trackers.retryOutcome(tracker, input) (requires review) retries a skipped admission.
    • management.trackers.records(tracker, { member }) works on wallet trackers, returning the member's linked wallets' records. trackers.ingest results include admissions counts.
    • New ingest permission, granted to hosted-app keys: it allows custom tracker ingestion only. People keep ingesting with observe.

    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.

  • ea5d69e: Quests can check token balances of a member's linked wallets.

    • 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.
    • Authoring: parseUnits() and formatUnits() convert whole-token amounts, and WalletBalanceQuery and WalletBalance type the call.
    • Contract: evmChains, evmChainSchema and walletBalanceSchema.
    • Management: the member detail's balanceChecks lists the member's recent recorded balance reads; Console shows them under the member's wallets.
    • Snippet: token-holder checks that a member's linked wallets hold at least a minimum.

    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.

  • 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.

    • relay.auth.wallet.signIn(signer), and its two steps start(input) and verify(proof). relay.auth.providers() returns wallets, the networks the project enabled.
    • 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.
    • 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.
    • Contract: walletSignInNetworkSchema, walletSignInNetwork(), isWalletSignInRoute() and memberSummarySchema.
    • 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.

    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.

  • b22ead4: X trackers can trigger quests, and budgets can reset weekly.

    • 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.
    • 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 }.
    • budget({ period: "week" }) limits completions or points per ISO week in UTC, starting Monday, by the attempt's eligibility time.
    • 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".

    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.

Patch Changes

  • 0b5ee21: domino dev runs against a Domino API that reports direct preview transport, such as Domino's local development stack, without cloudflared: the preview routes straight to the app's port, so the CLI starts no connector and skips the public reachability check. On such a stack without Workers AI credentials, domino dev keeps running on the quests already published instead of stopping. developmentPreflightSchema gains development.transport ("tunnel" or "direct"), defaulting to "tunnel" for APIs that do not report it, so existing behavior is unchanged.
  • Updated dependencies [6634992]
  • Updated dependencies [531f41e]
  • Updated dependencies [c71712f]
  • Updated dependencies [ea5d69e]
  • Updated dependencies [e7853fb]
  • Updated dependencies [b22ead4]
    • @domino-sdk/relay@0.25.0

0.24.0

Minor Changes

  • 305ca73: Hosted apps can now have secrets. Add domino secrets set NAME, domino secrets list, and domino secrets delete NAME to manage them in the project's test environment. set reads the value from stdin or a hidden terminal prompt, never from arguments, and never prints it. Worker builds (app.kind: "worker") staged after a change receive every secret as a secret binding, replacing an app vars entry of the same name; run domino stage again to apply a change. Static apps and domino dev receive none.

    • The management client adds secrets.list(), secrets.set(name, value), and secrets.delete(name), for GET /management/v1/secrets, PUT /management/v1/secrets/:name with { value }, and DELETE /management/v1/secrets/:name. Values are write-only: no route returns them.
    • Worker apps declare the secrets they read in relay.json app.secrets: { "APP_API_KEY": { "description": "…", "required": true } }. required defaults to true. A name cannot be both a vars entry and a declared secret, and static apps cannot declare any. domino stage warns on stderr about each required declared secret that is not set, with the command that sets it, and adds missingSecrets to its result; the build still stages. domino secrets list returns the set secrets, the declared ones (the active build's merged with this checkout's relay.json), and missing. GET /management/v1/secrets includes the active build's declared secrets.
    • New exports: appSecretNameSchema, appSecretValueSchema, appSecretInputSchema, appSecretSchema, appSecretListSchema, appSecretDeclarationSchema, appSecretDeclarationsSchema, the AppSecretDeclaration type, and APP_SECRET_LIMIT.
    • Names use uppercase letters, digits, and underscores (at most 64 characters, not starting with a digit); DOMINO_ names and ASSETS are reserved. Values are 1 byte to 5 KiB, and a project holds at most 50. Setting and deleting need publish, listing needs read. Other environments answer 422.

    Upgrade notes:

    • Additive. Existing commands, client methods, and vars keep working. Older CLIs reject a relay.json that declares app.secrets; upgrade the CLI before adding declarations.
    • The commands need a server with this release; older servers answer 404. A server without app secret storage configured answers 503.
    • Move credentials you placed in relay.json app.vars into secrets and remove them from the repository. The API starter now declares APP_API_KEY; set it this way (domino secrets set APP_API_KEY, then domino stage) to open its protected routes on the staged preview.
    • Refresh installed agent guidance with domino agents install after upgrading.

Patch Changes

  • @domino-sdk/relay@0.24.0

0.23.1

Patch Changes

  • e4d0a4f: Fix domino dev missing rapid, same-size source edits on filesystems that report identical modification timestamps. Source polling now compares file contents as well as metadata. No configuration or migration is required.
  • @domino-sdk/relay@0.23.1

0.23.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • 49fa0fd: Apps can now match the ledger and tiered reward IDs that reads return to the keys they declared.

    • relay.pointLedgers.list() (GET /v1/point-ledgers) and the React hook usePointLedgers() return each ledger's id, key, and name.
    • relay.tieredRewards.list() (GET /v1/tiered-rewards) and useTieredRewards() return each tiered reward's id and key.
    • 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.

    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.

    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.

    Upgrade notes:

    • 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.
    • TypeScript code that builds quest views, such as test fixtures, must add kind and that kind's fields.
    • 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.
    • No database migration is required.

Patch Changes

  • Updated dependencies [49fa0fd]
    • @domino-sdk/relay@0.23.0

0.22.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • 024b075: Add domino feedback for humans and agents to report tooling issues to Domino's Linear Triage queue. Submit text directly, from a file, or from stdin; use --dry-run to review the report and --json for automation. The bundled agent skill now instructs agents to report tooling problems and include the issue link in their handoff.

    At task completion, agents also review and report difficulties that better SDK or CLI support could address, including manual work and workarounds during otherwise successful tasks. They skip duplicates and reports with no tooling difficulty to describe.

    Requires Domino authentication and a backend with feedback delivery configured by the operator. Existing commands remain compatible. Refresh installed agent guidance with domino agents install after upgrading.

  • 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.

    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".

Patch Changes

  • Updated dependencies [2614ad4]
    • @domino-sdk/relay@0.22.0

0.21.0

Not published to npm. Its changes first reached npm in 0.23.1.

Minor Changes

  • b605061: Let organization admins delete a project or move it to another organization. Add deleteProject (DELETE /management/v1/project) and transferProject (POST /management/v1/project/transfer) to the management client, with the projectDeletion* and projectTransfer* schemas. Both need the project's name as confirm. A deleted project stops answering at once. Domino support can restore it for 30 days, after which its data, hosted app, repository and evidence are erased. Only a project without participants can move, and its hosted app keeps working without a redeploy: a hosted app key now decides its organization, so a stale X-Relay-Organization from the app is ignored rather than refused. When a project moved, management routes tell members of its new organization where it went (PROJECT_MOVED), and the CLI offers to follow it: it signs in to that organization if needed, updates the checkout and retries. domino login --organization <id> does the same without a prompt.

Patch Changes

  • @domino-sdk/relay@0.21.0

0.20.0

Not published to npm. Its changes first reached npm in 0.23.1.

Patch Changes

  • Updated dependencies [2ff82fb]
    • @domino-sdk/relay@0.20.0

0.19.0

Minor Changes

  • cea3ca7: Require sign-in on every staging app. Visitors sign in with Domino Console and need read access to the project in Test, or enter a password that a project owner sets under Settings → Staging access. Console's Open app links sign in directly. Add the stagingAccess* and stagingLink schemas for GET/PUT /management/v1/staging/access and POST /management/v1/staging/link. This is a breaking change for scripts that fetch staging URLs without a session. They should mint a link with POST /management/v1/staging/link and send the cookie it sets. Staging now reserves the /__domino/staging-access path, including for transparent routing.

Patch Changes

  • c2041e7: Fix playground and domino dev previews crashing with ENOSPC: System limit for number of file watchers reached. Quest synchronization now polls source files about once a second and skips dependency, build, and runtime directories before scanning. App hot reload still uses the app's development server. No configuration changes are required; upgrade the CLI and restart affected previews.
  • @domino-sdk/relay@0.19.0

0.18.4

Patch Changes

  • 68a0429: Use published npm packages for new starters instead of bundled SDK and CLI archives. Exports pin both packages to the same release and include a lockfile. Starter provenance now lives in .domino/starter.json, separate from legacy archive metadata.

    Doctor reports installed SDK and CLI versions and no longer requires obsolete starter archives. To migrate an existing project, replace Domino's file:.domino/*.tgz dependencies with the same exact published npm version, remove their pnpm.overrides entries, install, and run the app checks. Then delete the unused package archives and old .domino/release.json archive manifest. Preserve unrelated dependencies and overrides.

    The Playground offers Upgrade with AI for CLIs older than 0.18.3 or projects with local package pins before launching the preview. Dependencies change only when the owner selects the action.

  • @domino-sdk/relay@0.18.4

0.18.3

Patch Changes

  • da8c475: Fix previews that fail after Vite reports ready. Local and public startup checks now request HTML, so apps with a browser-only page fallback, including the Classic starter, can start successfully. Upgrade the project's CLI and restart the preview; no app configuration changes are required.
  • @domino-sdk/relay@0.18.3

0.18.2

Patch Changes

  • 0b04a5f: Install small Codex and Claude skill loaders that read guidance from the project-local CLI package. New starters point to the same bundled skill from AGENTS.md, and CLAUDE.md points to AGENTS.md. Guidance now follows dependency upgrades without routine skill reinstallation. Agents must re-read the skill after upgrading, and dependencies must be installed before the loader target is available.

    Existing checkouts should upgrade their project-local CLI, run domino agents install once, and commit the instruction changes. The installer migrates known unmodified copies, keeps package-backed redirects for old reference links, and adds marked pointers to AGENTS.md and CLAUDE.md while preserving project instructions. It reports customized files before writing; move their project-specific rules to AGENTS.md, reconcile the reported files with the bundled skill, and retry. domino doctor distinguishes missing package guidance from copied or customized instructions. Installation results now include an updated file list, and new installations create loaders instead of copying all references.

    The bundled hosted workflow teaches agents to start from current main, preserve and reconcile resumed work, and integrate changes into main before shared staging. The CLI and backend do not yet enforce that branch policy or merge work automatically.

  • @domino-sdk/relay@0.18.2

0.18.1

Patch Changes

  • 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".

    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.

    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.

    Upgrade note: if relay.json apiUrl is set to a management proxy URL, change it to the participant API origin (https://relay.domino.run).

  • Updated dependencies [2186fe3]

    • @domino-sdk/relay@0.18.1

0.18.0

Minor Changes

  • 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.

    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.

  • 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.

  • 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.

    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.

  • 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.

    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.

  • 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.

    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.

    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.

  • 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.

    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.

  • 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.

    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.

    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.

  • 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.

    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.

    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.

    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.

  • 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.

Patch Changes

  • Updated dependencies [bf84375]
  • Updated dependencies [ac63da2]
  • Updated dependencies [0a0ac9a]
  • Updated dependencies [40c9761]
  • Updated dependencies [b596a0a]
  • Updated dependencies [e5b7091]
  • Updated dependencies [243d9e7]
  • Updated dependencies [4cea332]
  • Updated dependencies [2a7ca88]
    • @domino-sdk/relay@0.18.0

0.17.2

Patch Changes

  • 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.

    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.

    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.

  • Updated dependencies [664eb80]

    • @domino-sdk/relay@0.17.2

0.17.1

Patch Changes

  • ff2c1ee: Remove the packaged project-maps agent reference. Console now draws the interaction map, user flow, and decision matrix from published project state under Project map. domino agents install no longer adds references/project-maps.md; an installed copy can be deleted.
  • @domino-sdk/relay@0.17.1

0.17.0

Minor Changes

  • 052417c: Add packaged agent guidance for creating evidence-backed interaction maps, user flows, decision matrices, and board-ready project views.

Patch Changes

  • @domino-sdk/relay@0.17.0

0.16.1

Patch Changes

  • aedef4f: Fix classic, waitlist, and referral starter compatibility with text and URL submission types. Quiz recovery now reads only quiz attempts, and classic interaction labels cover the expanded SDK union. Supported interactions remain unchanged.
  • @domino-sdk/relay@0.16.1

0.16.0

Not published to npm. Its changes first reached npm in 0.16.1.

Minor Changes

  • 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.

    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.

    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.

  • 074c292: Add captured X verification to authored quest evaluators: x.connection(), x.profile(), x.post({ postId }), x.follows({ userId }), and x.reposted({ postId }).

    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.

Patch Changes

  • 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.

    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.

  • Updated dependencies [abc6358]

  • Updated dependencies [3280aa1]

  • Updated dependencies [074c292]

    • @domino-sdk/relay@0.16.0

0.15.0

Minor Changes

  • 6c309ea: Hosted apps can call the management API from their own server code without a pasted credential. Every staged Worker build now receives a platform-managed key as the secret binding DOMINO_MANAGEMENT_KEY, plus DOMINO_API_URL, DOMINO_ORGANIZATION, DOMINO_PROJECT, and DOMINO_ENVIRONMENT. Build a client with hostedManagementClient(env) from @domino-sdk/relay-cli, which is safe to bundle into a Worker without nodejs_compat. The key (dhk_…) has exactly read and points in the project's test environment, acts as hosted:<repository ID> rather than a person, and is revoked when a newer build activates or its own build fails.

    domino dev now requests a 12-hour development key (it needs your own read and points permissions in the test environment), exports the same five variables to the dev server process, and revokes the key when it stops. An app's vite.config.ts must forward them to the local Worker in the cloudflare() plugin's development vars; apps created from earlier starters need that addition.

    Upgrade notes: the five DOMINO_ names are now reserved; app vars with those names are replaced by Domino's values. Deploy the backend (migration 0053_hosted_app_keys) and API Worker before the builder, because the builder fails a Worker deployment when it cannot obtain a key. Older CLIs keep working and simply do not export a development key; a new CLI against an older API prints that no key is available and continues.

  • 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.

    @domino-sdk/relay/react provides the React layer the starters share:

    • RelayProvider, useExperience, questGroups, useQuest, and the quest status helpers
    • useSignIn, useSignInMethods, useSignOut, and useAccounts
    • useLeaderboard, useReferralSummary, useInvitation, and useRelay

    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.

    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.

Patch Changes

  • Updated dependencies [6c309ea]
    • @domino-sdk/relay@0.15.0

0.14.1

Patch Changes

  • 9f6c8db: Remove CLI and agent guidance for the retired Console Releases page and JSON imports. Use domino deploy --environment test for code-defined behavior and domino stage for hosted test apps. Quest Studio publication remains available for Console-authored quests.

    Code-defined live publication through Console is no longer available. Existing live definitions keep running. The CLI continues to reject live publication; domino deploy --preview --environment live still validates without publishing. Offline bundle exports remain supported.

  • @domino-sdk/relay@0.14.1

0.14.0

Minor Changes

  • 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.

    Requires the matching backend deployment. Existing clients and manifests are unaffected.

Patch Changes

  • Updated dependencies [11abaf5]
    • @domino-sdk/relay@0.14.0

0.13.0

Minor Changes

  • 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.

    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.

Patch Changes

  • Updated dependencies [e969f1e]
    • @domino-sdk/relay@0.13.0

0.12.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [9fdc113]
    • @domino-sdk/relay@0.12.0

0.11.0

Minor Changes

  • 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.

    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.

    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.

Patch Changes

  • Updated dependencies [5337971]
    • @domino-sdk/relay@0.11.0

0.10.0

Patch Changes

  • Updated dependencies [8a81fde]
  • Updated dependencies [d5d530d]
    • @domino-sdk/relay@0.10.0

0.9.1

Patch Changes

  • a024053: Link doctor output and bundled agent guidance to the public package changelog and upgrade instructions. When SDK and server behavior disagree, agents can review intervening release notes, upgrade packages together, and retry the failed action. Guidance is advisory and does not automatically update dependencies or claim server compatibility.
  • @domino-sdk/relay@0.9.1

0.9.0

Minor Changes

  • 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.

    New release and catalog publication accept only Workers AI. Historical provider identifiers remain readable for retained records, but cannot select simulated evaluation.

Patch Changes

  • Updated dependencies [6a5b6d3]
    • @domino-sdk/relay@0.9.0

0.8.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [25ea110]
    • @domino-sdk/relay@0.8.0

0.7.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [0bbefc1]
    • @domino-sdk/relay@0.7.0

0.6.0

Minor Changes

  • 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.
  • 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.
  • d7dd5a4: Show readable command results by default and offer interactive project selection for checkout, init, and inspection commands. Prompt for missing create inputs, agent selection, and recent build selection. Confirm deployment and staging in terminals, with --yes to skip confirmation. Use --json for structured results and --no-interactive to disable terminal prompts.
  • 7c88acd: Add people to management list reads and a people(ids) client method. Both return the sign-in accounts and profiles of members that records name only by ID, so tools can show who a member is outside the Members list.
  • 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.

Patch Changes

  • 7216980: Report only staging blockers when staging preflight fails. Do not include optional participant sign-in diagnostics in the error.
  • Updated dependencies [5cd39a2]
  • Updated dependencies [5cd39a2]
  • Updated dependencies [5cd39a2]
    • @domino-sdk/relay@0.6.0

0.5.0

Minor Changes

  • 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.

  • 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.

    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.

Patch Changes

  • 8b454af: Show configuration sources and pinned install commands in setup diagnostics, label the bundled capability catalog with release provenance, and preserve Git checkout failure details. Clarify recovery and verification in the shipped agent guidance. Create missing parent directories for deployment build output.
  • Updated dependencies [85bb8aa]
  • Updated dependencies [c24042e]
  • Updated dependencies [990c756]
    • @domino-sdk/relay@0.5.0

0.4.0

Minor Changes

  • 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.
  • 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.
  • 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.
  • 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.

Patch Changes

  • d8a2572: Guide agents to discover recipes from the live snippet catalog before authoring quests. Expose the workflow through domino reference authoring and link it from generated project guidance.
  • 44e1436: Guide agents to research branding and campaign context, propose a visual direction and campaign brief, and wait for owner confirmation before the first implementation. Preserve confirmed decisions for later sessions.
  • 22e5523: Describe the rebuilt starter in the hosted-project agent reference: a static TanStack Router and Tailwind CSS v4 app with a neutral look, sample tour quests and src/relay hooks for session, quest state, submission, and recovery.
  • Updated dependencies [d8a2572]
  • Updated dependencies [c475d40]
  • Updated dependencies [8ead9ee]
  • Updated dependencies [7204e04]
  • Updated dependencies [1fb2edd]
    • @domino-sdk/relay@0.4.0

0.3.0

Minor Changes

  • 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.

Patch Changes

  • Updated dependencies [53d6aef]
    • @domino-sdk/relay@0.3.0

0.2.0

Minor Changes

  • 9c037ee: Use hosted test data and leased HTTPS previews for development, with an explicit offline runtime option. Fix unified deployment startup, preflight staging prerequisites, and bundle capability discovery, participant API, referral and troubleshooting references. Record exact starter package hashes and verify the distributed development journey.
  • 8aba51d: Save the project setup mode and brief, and include the brief in newly provisioned starter repositories. Console guides new and existing projects through agent handoff with resumable setup.

Patch Changes

  • @domino-sdk/relay@0.2.0

0.1.0

Minor Changes

  • d46d47c: Introduce the Relay SDK, Domino CLI with bundled agent skills, and standalone local runtime.

Patch Changes

  • Updated dependencies [d46d47c]
    • @domino-sdk/relay@0.1.0

On this page

SDK1.66.11.66.0Minor Changes1.65.0Minor Changes1.64.0Minor ChangesPatch Changes1.63.0Minor ChangesPatch Changes1.62.0Minor ChangesPatch Changes1.61.0Minor ChangesPatch Changes1.60.0Minor Changes1.59.0Minor ChangesPatch Changes1.58.0Minor ChangesPatch Changes1.57.0Minor Changes1.56.0Minor Changes1.55.0Minor ChangesPatch Changes1.54.0Minor Changes1.53.11.53.0Minor Changes1.52.11.52.0Minor Changes1.51.0Minor Changes1.50.0Minor Changes1.49.11.49.0Minor Changes1.48.0Minor Changes1.47.0Minor Changes1.46.0Minor Changes1.45.0Minor Changes1.44.0Minor ChangesPatch Changes1.43.0Minor Changes1.42.0Minor Changes1.41.0Minor Changes1.40.0Minor Changes1.39.1Patch Changes1.39.0Minor Changes1.38.1Patch Changes1.38.0Minor Changes1.37.11.37.0Minor Changes1.36.0Minor Changes1.35.0Minor Changes1.34.0Minor Changes1.33.0Minor Changes1.32.0Minor Changes1.31.0Minor ChangesPatch Changes1.30.11.30.0Minor Changes1.29.0Minor Changes1.28.0Minor Changes1.27.11.27.0Minor Changes1.26.0Minor Changes1.25.11.25.0Minor Changes1.24.11.24.0Minor Changes1.23.21.23.1Patch Changes1.23.0Minor Changes1.22.0Minor Changes1.21.0Minor Changes1.20.0Minor Changes1.19.0Minor Changes1.18.1Patch Changes1.18.0Minor Changes1.17.0Minor Changes1.16.0Minor Changes1.15.11.15.01.14.0Minor ChangesPatch Changes1.13.0Minor Changes1.12.0Minor Changes1.11.2Patch Changes1.11.1Patch Changes1.11.0Minor Changes1.10.1Patch Changes1.10.0Minor Changes1.9.0Minor Changes1.8.0Minor Changes1.7.0Minor Changes1.6.0Minor ChangesPatch Changes1.5.21.5.11.5.0Minor Changes1.4.0Minor Changes1.3.0Minor Changes1.2.0Minor Changes1.1.1Patch Changes1.1.0Minor Changes1.0.0Major ChangesMinor ChangesPatch Changes1.0.0-next.34Major ChangesMinor ChangesPatch Changes1.0.0-next.33Minor Changes1.0.0-next.32Minor Changes1.0.0-next.31Minor Changes1.0.0-next.30Minor Changes1.0.0-next.29Minor Changes1.0.0-next.28Patch Changes1.0.0-next.27Minor Changes1.0.0-next.26Minor Changes1.0.0-next.251.0.0-next.24Major Changes1.0.0-next.23Major Changes1.0.0-next.22Major Changes1.0.0-next.21Minor Changes1.0.0-next.201.0.0-next.19Patch Changes1.0.0-next.18Minor Changes1.0.0-next.17Major Changes1.0.0-next.16Major Changes1.0.0-next.15Major Changes1.0.0-next.14Major Changes1.0.0-next.13Major Changes1.0.0-next.12Minor Changes1.0.0-next.11Minor ChangesPatch Changes1.0.0-next.101.0.0-next.9Minor Changes1.0.0-next.8Major Changes1.0.0-next.7Major Changes1.0.0-next.6Patch Changes1.0.0-next.51.0.0-next.4Minor Changes1.0.0-next.3Major ChangesPatch Changes1.0.0-next.2Minor ChangesPatch Changes1.0.0-next.11.0.0-next.0Major Changes0.31.10.31.0Minor Changes0.30.0Minor Changes0.29.0Minor Changes0.28.0Minor Changes0.27.0Minor Changes0.26.00.25.10.25.0Minor Changes0.24.00.23.10.23.0Minor Changes0.22.0Minor Changes0.21.00.20.0Minor Changes0.19.00.18.40.18.30.18.20.18.1Patch Changes0.18.0Minor Changes0.17.2Patch Changes0.17.10.17.00.16.10.16.0Minor Changes0.15.0Minor Changes0.14.10.14.0Minor Changes0.13.0Minor Changes0.12.0Minor Changes0.11.0Minor Changes0.10.0Minor ChangesPatch Changes0.9.10.9.0Minor Changes0.8.0Minor Changes0.7.0Minor Changes0.6.0Minor Changes0.5.0Minor Changes0.4.0Minor ChangesPatch Changes0.3.0Minor Changes0.2.00.1.0Minor ChangesCLI1.66.1Patch Changes1.66.0Minor ChangesPatch Changes1.65.0Minor ChangesPatch Changes1.64.0Minor ChangesPatch Changes1.63.0Minor ChangesPatch Changes1.62.0Minor ChangesPatch Changes1.61.0Minor ChangesPatch Changes1.60.0Minor ChangesPatch Changes1.59.0Minor ChangesPatch Changes1.58.0Minor ChangesPatch Changes1.57.0Minor ChangesPatch Changes1.56.0Minor ChangesPatch Changes1.55.0Minor ChangesPatch Changes1.54.0Minor ChangesPatch Changes1.53.1Patch Changes1.53.0Minor ChangesPatch Changes1.52.1Patch Changes1.52.0Minor ChangesPatch Changes1.51.0Patch Changes1.50.0Minor ChangesPatch Changes1.49.1Patch Changes1.49.0Minor ChangesPatch Changes1.48.0Minor ChangesPatch Changes1.47.0Minor ChangesPatch Changes1.46.0Minor ChangesPatch Changes1.45.0Minor ChangesPatch Changes1.44.0Patch Changes1.43.0Minor ChangesPatch Changes1.42.0Minor ChangesPatch Changes1.41.0Patch Changes1.40.0Minor ChangesPatch Changes1.39.1Patch Changes1.39.0Patch Changes1.38.1Patch Changes1.38.0Minor ChangesPatch Changes1.37.1Patch Changes1.37.0Minor ChangesPatch Changes1.36.0Patch Changes1.35.0Minor ChangesPatch Changes1.34.0Minor ChangesPatch Changes1.33.0Minor ChangesPatch Changes1.32.0Minor ChangesPatch Changes1.31.0Minor ChangesPatch Changes1.30.1Patch Changes1.30.0Minor ChangesPatch Changes1.29.0Minor ChangesPatch Changes1.28.0Minor ChangesPatch Changes1.27.1Patch Changes1.27.0Minor ChangesPatch Changes1.26.0Patch Changes1.25.1Patch Changes1.25.0Patch Changes1.24.1Patch Changes1.24.0Minor ChangesPatch Changes1.23.2Patch Changes1.23.1Patch Changes1.23.0Minor ChangesPatch Changes1.22.0Minor ChangesPatch Changes1.21.0Minor ChangesPatch Changes1.20.0Minor ChangesPatch Changes1.19.0Minor ChangesPatch Changes1.18.1Patch Changes1.18.0Minor ChangesPatch Changes1.17.0Minor ChangesPatch Changes1.16.0Minor ChangesPatch Changes1.15.1Patch Changes1.15.0Minor ChangesPatch Changes1.14.0Minor ChangesPatch Changes1.13.0Patch Changes1.12.0Minor ChangesPatch Changes1.11.2Patch Changes1.11.1Patch Changes1.11.0Minor ChangesPatch Changes1.10.1Patch Changes1.10.0Patch Changes1.9.0Patch Changes1.8.0Minor ChangesPatch Changes1.7.0Patch Changes1.6.0Minor ChangesPatch Changes1.5.2Patch Changes1.5.1Patch Changes1.5.0Minor ChangesPatch Changes1.4.0Patch Changes1.3.0Patch Changes1.2.0Minor ChangesPatch Changes1.1.1Patch Changes1.1.0Minor ChangesPatch Changes1.0.0Major ChangesMinor ChangesPatch Changes1.0.0-next.34Major ChangesMinor ChangesPatch Changes1.0.0-next.33Major ChangesPatch Changes1.0.0-next.32Patch Changes1.0.0-next.31Minor ChangesPatch Changes1.0.0-next.30Minor ChangesPatch Changes1.0.0-next.29Minor ChangesPatch Changes1.0.0-next.28Patch Changes1.0.0-next.27Minor ChangesPatch Changes1.0.0-next.26Minor ChangesPatch Changes1.0.0-next.25Minor ChangesPatch Changes1.0.0-next.24Major ChangesPatch Changes1.0.0-next.23Major ChangesPatch Changes1.0.0-next.22Major ChangesPatch Changes1.0.0-next.21Minor ChangesPatch Changes1.0.0-next.20Minor ChangesPatch Changes1.0.0-next.19Patch Changes1.0.0-next.18Minor ChangesPatch Changes1.0.0-next.17Patch Changes1.0.0-next.16Major ChangesPatch Changes1.0.0-next.15Major ChangesPatch Changes1.0.0-next.14Patch Changes1.0.0-next.13Major ChangesPatch Changes1.0.0-next.12Patch Changes1.0.0-next.11Minor ChangesPatch Changes1.0.0-next.10Minor ChangesPatch Changes1.0.0-next.9Minor ChangesPatch Changes1.0.0-next.8Major ChangesPatch Changes1.0.0-next.7Patch Changes1.0.0-next.6Patch Changes1.0.0-next.5Major ChangesPatch Changes1.0.0-next.4Minor ChangesPatch Changes1.0.0-next.3Major ChangesPatch Changes1.0.0-next.2Minor ChangesPatch Changes1.0.0-next.1Patch Changes1.0.0-next.0Major ChangesPatch Changes0.31.1Patch Changes0.31.0Minor ChangesPatch Changes0.30.0Minor ChangesPatch Changes0.29.0Minor ChangesPatch Changes0.28.0Minor ChangesPatch Changes0.27.0Minor ChangesPatch Changes0.26.0Minor ChangesPatch Changes0.25.1Patch Changes0.25.0Minor ChangesPatch Changes0.24.0Minor ChangesPatch Changes0.23.1Patch Changes0.23.0Minor ChangesPatch Changes0.22.0Minor ChangesPatch Changes0.21.0Minor ChangesPatch Changes0.20.0Patch Changes0.19.0Minor ChangesPatch Changes0.18.4Patch Changes0.18.3Patch Changes0.18.2Patch Changes0.18.1Patch Changes0.18.0Minor ChangesPatch Changes0.17.2Patch Changes0.17.1Patch Changes0.17.0Minor ChangesPatch Changes0.16.1Patch Changes0.16.0Minor ChangesPatch Changes0.15.0Minor ChangesPatch Changes0.14.1Patch Changes0.14.0Minor ChangesPatch Changes0.13.0Minor ChangesPatch Changes0.12.0Minor ChangesPatch Changes0.11.0Minor ChangesPatch Changes0.10.0Patch Changes0.9.1Patch Changes0.9.0Minor ChangesPatch Changes0.8.0Minor ChangesPatch Changes0.7.0Minor ChangesPatch Changes0.6.0Minor ChangesPatch Changes0.5.0Minor ChangesPatch Changes0.4.0Minor ChangesPatch Changes0.3.0Minor ChangesPatch Changes0.2.0Minor ChangesPatch Changes0.1.0Minor ChangesPatch Changes