Skip to content

DX-1211: Auth interface docstrings (prerequisites, side-effects, failure modes)#2242

Draft
umair-ably wants to merge 11 commits into
DX-1211/silent-failure-hintsfrom
DX-1211/auth-docstrings
Draft

DX-1211: Auth interface docstrings (prerequisites, side-effects, failure modes)#2242
umair-ably wants to merge 11 commits into
DX-1211/silent-failure-hintsfrom
DX-1211/auth-docstrings

Conversation

@umair-ably

Copy link
Copy Markdown
Contributor

Stacked on DX-1211/silent-failure-hints. Review the diff against that base, not main. Merge the parent first.

Applies docstringRules.md to the public Auth interface in ably.d.ts, surfacing the call-site prerequisites and failure modes a caller cannot infer from the type signature (the DX-1211 class of silent/architectural pitfall). Same effort as the already-landed RealtimeChannel/RealtimePresence pass; one subsystem = one PR.

Members (5 audited; 3 deprecated v1-callback overloads skipped)

Member What was surfaced
revokeTokens basic-auth (API key, not token) requirement; non-code "revocable tokens enabled on the key" prerequisite (+ feature-page @see)
requestToken token-issuing prerequisite (authCallback/authUrl/key); callback-contract detail offloaded to the @see
createTokenRequest a local API key must be available to sign the request
authorize re-auth side-effect (resolves once the token takes effect on a connected connection); token-issuing prerequisite; RSA10a key-immutability (authorize() cannot change the API key)
clientId adds the canonical @see

Conventions

  • Folded prose, no headings/labels, no em-dashes, no numeric error codes (failures framed as "rejects with an {@link ErrorInfo}").
  • Every member carries an @see to the canonical JS API reference.
  • Every claim re-derived from src/ (file:line) in an adversarial verify pass before applying.

Validation

  • npm run docs (TypeDoc, treatWarningsAsErrors) — clean (all {@link} resolve)
  • eslint ably.d.ts — exit 0 · prettier --check — clean

Reviewer notes / follow-ups

  • @see anchors: the canonical reference (ably/docs #3400) is not yet published, so 4 of 5 anchors are convention-derived per §6 and unverified (#revoke-tokens is corroborated). @see is not validated by TypeDoc, so re-check the slugs when #3400 ships.
  • clientId change is cosmetic (@see only) — the drafter's null/wildcard value claims were rejected by verification as inaccurate. Fine to drop if you'd rather treat it as a pure accessor.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47a8cfda-5939-412b-b263-a3f3155ad68f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DX-1211/auth-docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot temporarily deployed to staging/pull/2242/bundle-report June 9, 2026 14:23 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/2242/typedoc June 9, 2026 14:23 Inactive
@umair-ably umair-ably force-pushed the DX-1211/auth-docstrings branch from 4d45490 to c81b62a Compare June 9, 2026 14:31
@github-actions github-actions Bot temporarily deployed to staging/pull/2242/bundle-report June 9, 2026 14:32 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/2242/typedoc June 9, 2026 14:33 Inactive

@m-hulbert m-hulbert left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few suggestions on splitting up long sentences and large walls of text into paragraphs, but see what you think.

Comment thread ably.d.ts Outdated

/**
* Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} and {@link AuthOptions} passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} and {@link AuthOptions} objects passed in entirely replace, as opposed to being merged with, the current client library saved values.
* Instructs the library to get a new token immediately. On a realtime client it re-authenticates the live connection, or initiates a connection if not connected, and the returned promise resolves only once the new token has taken effect on a `connected` connection, rejecting with an {@link ErrorInfo} if re-authentication fails or the connection cannot be (re)established. The client must be able to issue tokens, so a `key`, `authUrl`, or `authCallback` must be configured in {@link ClientOptions}, otherwise the call rejects with an {@link ErrorInfo}; `authorize()` cannot change the API key, so passing an `authOptions.key` that differs from the one the client was constructed with also rejects. Any {@link TokenParams} and {@link AuthOptions} passed in are stored as the new defaults for all subsequent token requests and entirely replace, rather than merge with, the current saved values.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Instructs the library to get a new token immediately. On a realtime client it re-authenticates the live connection, or initiates a connection if not connected, and the returned promise resolves only once the new token has taken effect on a `connected` connection, rejecting with an {@link ErrorInfo} if re-authentication fails or the connection cannot be (re)established. The client must be able to issue tokens, so a `key`, `authUrl`, or `authCallback` must be configured in {@link ClientOptions}, otherwise the call rejects with an {@link ErrorInfo}; `authorize()` cannot change the API key, so passing an `authOptions.key` that differs from the one the client was constructed with also rejects. Any {@link TokenParams} and {@link AuthOptions} passed in are stored as the new defaults for all subsequent token requests and entirely replace, rather than merge with, the current saved values.
* Instructs the library to get a new token immediately.
*
* On a realtime client it re-authenticates the live connection, or initiates a connection if not currently connected. The returned promise resolves only once the new token has taken effect on a `connected` connection. It rejects with an {@link ErrorInfo} if re-authentication fails or the connection cannot be (re)established.
*
* The client must be able to issue tokens, so a `key`, `authUrl`, or `authCallback` must be configured in {@link ClientOptions}. If one of these isn't configured the call rejects with an {@link ErrorInfo}.
*
* `authorize()` cannot change the API key, so passing an `authOptions.key` that differs from the one the client was constructed with will be rejected.
*
* Any {@link TokenParams} and {@link AuthOptions} passed in are stored as the new defaults for all subsequent token requests and entirely replace, rather than merge with, the current saved values.

AFAIK you can create paragraphs using empty lines, which helps break up this wall of text. I'd also push for shorter sentences where possible as some of those got quite convoluted.

Comment thread ably.d.ts Outdated
): never;
/**
* Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenRequest} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication).
* Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Use this to implement an Ably Token request callback for use by other clients. An API `key` value must be available locally to sign the request, supplied either in the client's {@link ClientOptions} or as `key` in the `authOptions` argument; without one the call rejects with an {@link ErrorInfo}, since a token-authenticated client cannot construct token requests itself and must instead obtain the {@link TokenRequest} from the key owner. Both {@link TokenParams} and {@link AuthOptions} are optional; when omitted or `null`, the client library's stored defaults (those set at construction or by a later `authorize` call) are used, and any values passed in replace, rather than merge with, those defaults.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Use this to implement an Ably Token request callback for use by other clients. An API `key` value must be available locally to sign the request, supplied either in the client's {@link ClientOptions} or as `key` in the `authOptions` argument; without one the call rejects with an {@link ErrorInfo}, since a token-authenticated client cannot construct token requests itself and must instead obtain the {@link TokenRequest} from the key owner. Both {@link TokenParams} and {@link AuthOptions} are optional; when omitted or `null`, the client library's stored defaults (those set at construction or by a later `authorize` call) are used, and any values passed in replace, rather than merge with, those defaults.
* Creates and signs an Ably {@link TokenRequest} based on the specified {@link TokenParams} and {@link AuthOptions}. If no `TokenRequest` or `TokenParams` are specified it uses those previously stored by the library. Use this to implement an Ably Token request callback for use by other clients.
*
* An API `key` value must be available locally to sign the request, supplied either in the client's {@link ClientOptions} or as `key` in the `authOptions` argument. Without a `key` the call rejects with an {@link ErrorInfo}, since a token-authenticated client cannot construct token requests itself and must instead obtain the {@link TokenRequest} from the key owner.
*
* Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the client library's stored defaults (those set at construction or by a later `authorize()` call) are used, and any values passed in replace, rather than merge with, those defaults.

Comment thread ably.d.ts Outdated
): never;
/**
* Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication).
* Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both are optional; when omitted or `null`, the client's stored defaults are used, as specified in the {@link ClientOptions} at instantiation or later updated by an `authorize` request, and any values passed in are used instead of, rather than merged with, those defaults. The client must have a usable way to obtain a token, so the resolved {@link AuthOptions} must include one of `authCallback`, `authUrl`, or `key`; a client given only a literal token or `tokenDetails` with no renewal mechanism cannot request a new token and the call rejects with an {@link ErrorInfo}.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both are optional; when omitted or `null`, the client's stored defaults are used, as specified in the {@link ClientOptions} at instantiation or later updated by an `authorize` request, and any values passed in are used instead of, rather than merged with, those defaults. The client must have a usable way to obtain a token, so the resolved {@link AuthOptions} must include one of `authCallback`, `authUrl`, or `key`; a client given only a literal token or `tokenDetails` with no renewal mechanism cannot request a new token and the call rejects with an {@link ErrorInfo}.
* Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both properties are optional. When omitted or `null`, the client's stored defaults are used, as specified in the {@link ClientOptions} at instantiation or later updated by an `authorize()` request. Any values passed in are used instead of, rather than merged with, those defaults.
*
* The client must have a way to obtain a token, so the resolved {@link AuthOptions} must include one of `authCallback`, `authUrl`, or `key`. A client given only a literal token or `tokenDetails` with no renewal mechanism cannot request a new token and the call rejects with an {@link ErrorInfo}.

On the 2nd paragraph I've made here - unsure if those 2 sentences are supposed to be related. They were separated by a semi-colon but I'm not sure they are.

Small note here on inconsistency between the descriptions too in how we refer to the default values and authorize().

Could argue whether the user needs to know that it's calling the REST API, but I won't go and rewrite everything as that's not the point 🙂

Comment thread ably.d.ts Outdated
/**
* Revokes the tokens specified by the provided array of {@link TokenRevocationTargetSpecifier}s. Only tokens issued by an API key that had revocable tokens enabled before the token was issued can be revoked. See the [token revocation docs](https://ably.com/docs/core-features/authentication#token-revocation) for more information.
* Revokes the tokens specified by the provided array of {@link TokenRevocationTargetSpecifier}s.
* The client must be authenticated with an API key (basic auth), not a token; a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads a bit confusing as it makes it sound like the client you're trying to revoke a token for needs to be authenticated by a key.

@umair-ably umair-ably force-pushed the DX-1211/silent-failure-hints branch from 7c617fe to 729d8f0 Compare June 25, 2026 10:47
@umair-ably umair-ably force-pushed the DX-1211/auth-docstrings branch from c81b62a to 75ca7f8 Compare June 25, 2026 10:47
@github-actions github-actions Bot temporarily deployed to staging/pull/2242/bundle-report June 25, 2026 10:48 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/2242/typedoc June 25, 2026 10:49 Inactive
umair-ably added a commit that referenced this pull request Jun 25, 2026
Apply the docstrings-standards.md review pass to the Auth interface,
incorporating m-hulbert's PR #2242 suggestions:

- Split walls of text into blank-line paragraphs, shorten sentences,
  and remove semicolons from descriptions (A1/A2).
- authorize: five paragraphs with active reject voice and an ErrorInfo
  on each failure mode.
- createTokenRequest: promote the defaults parenthetical to a sentence
  and correct the reviewer's "TokenRequest" arg slip (the method takes
  no TokenRequest parameter).
- requestToken/createTokenRequest: one verbatim stored-defaults
  phrasing, and authorize() with parentheses (C5/F2).
- revokeTokens: anchor the basic-auth prerequisite to the calling
  client to remove the ambiguity m-hulbert flagged (D5).
- clientId: drop the inline identified-clients link now that the
  canonical @see is present (E1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
umair-ably and others added 11 commits June 25, 2026 17:46
Declaration only — no runtime use yet. Documented silent-failure paths
will read this option in subsequent commits to gate a hint-carrying
throw; the default stays `false` in v2.x. Per DXRFC-022 work item B5
the default flips to `true` in v3 with no per-call opt-out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The guard at line 73 parenthesised `(state === 'attached' && _mode & flag) === 0`
so the `=== 0` compared against `(boolean && number)`. It happened to behave
correctly — `false === 0` is `false` so the throw skipped when not attached,
and the bitwise result === 0 was correct when attached — but the comparison
was structural luck rather than the obvious reading of the predicate.
Re-parenthesise to `state === 'attached' && (_mode & flag) === 0`.

Also emit an always-on warning log adjacent to the throw so the diagnostic
fires in the SDK output even when the caller swallows the throw. No
silentFailureLogSuffix here because this throw is unconditional (pre-DXRFC-022)
and not strictMode-gated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A short suffix appended to silent-failure warning logs emitted when
clientOptions.strictMode is off, so the reader knows the same path will
throw in a future major version. Co-locating on Logger keeps the import
surface tight; callers do `Logger.silentFailureLogSuffix()` next to
`Logger.logActionNoStrip(...)`.

Log-only by design — the suffix is not put into ErrorInfo.hint, because
the hint is also shown when the throw fires (strictMode on), where the
suffix would be misleading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a channel was attached without the presence_subscribe mode, the
server never delivers presence members, so presence.get() resolves to
[] regardless of who is actually present. Today there is no signal
distinguishing "no one is present" from "this client cannot see anyone".

This commit detects the case after ensureAttached() populates the
server-granted mode set, then:

- emits an always-on warning log carrying the hint + a suffix telling
  the reader that strictMode will throw in a future major version.
- throws ErrorInfo with err.hint when clientOptions.strictMode === true.

Code 93002 sits next to 93001 (annotation_subscribe missing) in the
SDK-internal precondition class. It would also be defensible to use
40160 (server-side capability denied), but the hint-coverage rubric
already pins 40160 to the "no auth options" hint shape, so a second
40160 throw site would either weaken that pin or need a rubric refactor.

Suspended-state and {waitForSync: false} paths return earlier and are
unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a channel was attached without the subscribe mode, the server
never delivers messages to the listener, so channel.subscribe()
appears to succeed but no callback ever fires. This commit closes the
gap symmetrically to presence.get() in the previous commit:

- After the implicit attach completes (RTL7g), if subscribe mode was
  not granted by the server, emit an always-on warning log carrying
  the hint + the future-throw suffix.
- One-shot per channel: the warning fires once per attach cycle to
  keep noise down on long-lived listeners. Reset _silentSubscribeWarned
  on the ATTACHED message so a channels.release() + re-attach with
  corrected modes restores signalling.
- Throw ErrorInfo (code 93003) when clientOptions.strictMode === true.

attachOnSubscribe: false is out of scope — the check requires an
attach to have populated _mode. Document this caveat separately if
needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Terser, technical error messages for 93002/93003
- Hints now offer setOptions() or omitting modes + capability check;
  drop confusing channel.modes note
- Keep listener registered on strict-mode throw (matches existing
  subscribe semantics)
- Remove ticket IDs from test names

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…grade

The 93001 hint lumped two distinct failure modes under "if the subsequent
attach is rejected": a missing namespace rule (Mutable Messages) does reject
the attach, but a missing annotation-subscribe capability does NOT - the
server silently drops the mode and this same error re-fires. Confirmed by
live sandbox traces and the canonical error registry.

Reword to key off the symptom the caller observes: attach rejected => enable
the namespace rule; attach succeeds but annotations still undelivered =>
grant the capability. Addresses review feedback that the hint was
presumptuous and over-enumerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… off colliding 93002

Two fixes to the presence.get()/channel.subscribe() missing-mode errors:

- Hint correctness (review feedback): the presence hint named a
  "presence-subscribe" capability that does not exist. Presence delivery is
  governed by the "subscribe" capability, matching the subscribe-mode hint.

- Error-code collision: 93002 is the canonical server code for "namespace
  needs Mutable Messages" (ably-common errors.json, faqs.ably.com/error-code-93002).
  presence.get() reused it client-side. Move presence to 91008 (presence
  block, next to 91005) and channel.subscribe() to 90009 (channel block);
  93xxx is the annotations/mutable-messages block. Both codes are new on this
  unreleased branch, so the renumber is non-breaking. Reserved in
  ably/ably-common#345. Update the two tests asserting them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts, failure modes)

Apply docstringRules.md to the public Auth interface in ably.d.ts so silent and
architectural call-site prerequisites are discoverable at the call site:

- revokeTokens: basic-auth (API key, not token) requirement; non-code "revocable
  tokens enabled on the key" prerequisite, with a feature-page @see.
- requestToken: token-issuing prerequisite (authCallback/authUrl/key); callback
  contract detail (content-types, size flags) offloaded to the @see.
- createTokenRequest: a local API key must be available to sign the request.
- authorize: re-authenticates the live connection (resolves once the token takes
  effect on a connected connection), token-issuing prerequisite, and the RSA10a
  key-immutability rule (authorize() cannot change the API key).
- clientId: adds the canonical @see.

Folded prose, no numeric error codes; every behavioural method carries one simple
@example and every member an @see to the canonical JS API reference. TypeDoc
(treatWarningsAsErrors), eslint, and prettier are clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the docstrings-standards.md review pass to the Auth interface,
incorporating m-hulbert's PR #2242 suggestions:

- Split walls of text into blank-line paragraphs, shorten sentences,
  and remove semicolons from descriptions (A1/A2).
- authorize: five paragraphs with active reject voice and an ErrorInfo
  on each failure mode.
- createTokenRequest: promote the defaults parenthetical to a sentence
  and correct the reviewer's "TokenRequest" arg slip (the method takes
  no TokenRequest parameter).
- requestToken/createTokenRequest: one verbatim stored-defaults
  phrasing, and authorize() with parentheses (C5/F2).
- revokeTokens: anchor the basic-auth prerequisite to the calling
  client to remove the ambiguity m-hulbert flagged (D5).
- clientId: drop the inline identified-clients link now that the
  canonical @see is present (E1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@umair-ably umair-ably force-pushed the DX-1211/auth-docstrings branch from 2a86111 to 7906161 Compare June 25, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants