Releases: hypercerts-org/ePDS
Release list
ePDS@0.7.0
Who should read this release
- Operators:
- Email delivery logs use a consistent recipient field.
- OTP send logs now record how long the email handoff took, the provider's message ID, and the SMTP server response.
- Optional email-event logging for operators who choose the third-party Resend service to send email; ePDS continues to support other email providers.
Minor Changes
-
#197
ce25bebThanks @aspiers! - Email delivery logs use a consistent recipient field.Affects: Operators
Operators: All four email delivery paths now log the recipient as
email: client-branded OTP, standard sign-in OTP, welcome OTP, and backup-email verification. Update structured-log queries forSent client-branded OTP emailto reademailinstead ofto. -
#203
2193515Thanks @aspiers! - OTP send logs now record how long the email handoff took, the provider's message ID, and the SMTP server response.Affects: Operators
Operators: every OTP email completion line (all four paths — client-branded, sign-in, welcome, backup-email verification) now carries
elapsedMs,messageId, andsmtpResponsealongsideemail.messageIdlets these logs be joined to Resend delivery events;elapsedMsisolates a slow SMTP handoff from other causes of late-arriving codes. Failed sends logelapsedMstoo, on the existingbetter-auth: failed to send OTP emailerror line. -
#198
2bf9af4Thanks @aspiers! - Optional email-event logging for operators who choose the third-party Resend service to send email; ePDS continues to support other email providers.Affects: Operators
Operators: ePDS remains compatible with any SMTP provider and does not require Resend. Operators who already use Resend can opt in by registering
https://<AUTH_HOSTNAME>/webhooks/resendfor the documented email events and settingRESEND_WEBHOOK_SECRET; logs include delivery, open, complaint, suppression, and scheduling events. The route verifies each webhook, logs only events whose sender exactly matchesSMTP_FROM, acknowledges unsupported click and inbound events without logging their payloads, and does not persist webhook data.
ePDS@0.6.5
Who should read this release
- End users:
- Operators:
Patch Changes
-
#187
dcc9590Thanks @aspiers! - Signing in through the demo no longer silently fails if you take a while to enter your emailed code.Affects: End users, Operators
End users: If you requested a sign-in code, then took several minutes to fetch it from your email before entering it, the demo could fail at the last step with a generic "Authentication failed" message — even though your code was correct. That happened because the demo started its 10-minute sign-in timer before it sent the code, so it could forget the sign-in while a later-issued code was still valid. The demo now remembers your sign-in for up to an hour, and if it genuinely does time out you now see "Your sign-in took too long to finish. Please sign in again." instead of a message that made it look like you typed the code wrong.
Operators: The demo client's
oauth_statecookiemaxAgeis raised from600(10 min) to60 * 60(1 hour), matching the auth service'sauth_flowrow TTL. Previously, the cookie's timer started when the OAuth flow began, while the OTP's 600-second validity started only when the code was issued, so the cookie could expire first. The OAuth callback now maps a missing/expired state cookie to/?error=session_expiredrather than/?error=auth_failed;session_expiredrenders as "Your sign-in took too long to finish. Please sign in again." -
#187
e1c9d06Thanks @aspiers! - Sign-in now stays active while you retrieve your emailed code and offers a clear way to restart if it times out.Affects: End users
End users: Previously, taking more than about five minutes to enter a code could leave the surrounding sign-in expired even though the code was still valid. The code-entry page now keeps the sign-in active while it remains open. If browser suspension or a longer interruption still makes the sign-in unrecoverable, the page disables code entry and Use different email, hides Resend code, and shows Start over; clicking it returns to the app you came from so you can begin again.
ePDS@0.6.4
Who should read this release
- Client app developers:
- Operators:
Patch Changes
-
#189
008b143Thanks @aspiers! - Failed one-time code attempts now appear in the server logs, split by reason and tagged with the account's email.Affects: Operators
Operators: failed OTP verifications are now logged under the
auth:better-authlogger.- Each line carries an
email,statusCode, andpathfield, and names the reason:code expired,invalid or unrecognized code, ortoo many attempts, code invalidated. - Routine failures (expired / invalid) log at
info; too-many-attempts logs atwarn. - All are visible at the default
infolevel — noLOG_LEVELchange needed.
- Each line carries an
-
#176
a00cb25Thanks @aspiers! - The/preview/validatepage now checksepds_handle_login_urlon your client metadata.Affects: Client app developers
Client app developers: a new
handle-login-urlrow joins the existing field checks.- Missing or empty value warns you that the "Or sign in with ATProto/Bluesky" button won't render.
- An
http(s)://value is ok, matching theisSafeHttpUrlgate that renders the button at runtime (http://accepted so localhost dev clients still pass). - Any other value (
javascript:,file:, unparseable) errors, because the runtime gate would silently drop the button on real flows.
ePDS@0.6.3
Who should read this release
Patch Changes
-
#169
cc65707Thanks @Kzoeps! - The final sign-in screen now lines up its message card with the page title.Affects: End users
End users: After approving an app sign-in, the "Login complete" screen now keeps the "You are being redirected..." card visually aligned with the title instead of sitting slightly lower on the page.
ePDS@0.6.2
Who should read this release
- End users:
Patch Changes
-
#154
2e4d327Thanks @aspiers! - Sign-in pages no longer strand users on a "session expired" dead end, and Resend no longer offers codes that won't work.Affects: End users
End users: if your sign-in times out (you closed the tab and came back, or your wait was longer than the page can keep alive in the background), you are now taken back to the app you were signing in to so it can offer you a retry. The page also no longer offers Resend in the rare case where the new code wouldn't work — instead it tells you the sign-in has timed out and gives you a Start over button. No more typing a fresh code that fails. If for some reason the automatic return is not possible, the page shows a "Return to sign in" button so you can get back to the app yourself in one click.
-
#154
b1fc940Thanks @aspiers! - Slow sign-ins are less likely to time out before you finish entering your code.Affects: End users
End users: if you take a few minutes to find your sign-in code in your inbox before entering it, you will no longer be bounced to a "session expired" page when you submit it. Closing the tab or walking away for a long stretch can still expire the flow, in which case the existing error pages still apply — but reading email at human speed should not.
ePDS@0.6.1
Who should read this release
- End users:
- "Powered by Certified" footer now appears on every auth-service page.
- "Use a different account" on the chooser now reliably takes you to the email form, not the code step for the previous account.
- Sign-in no longer fails with "Authentication session expired" when an OTP code is resent after the original code times out.
- Terms of Use and Privacy Policy links on the sign-in page now open in a new tab.
- OAuth consent buttons stack cleanly on small screens.
- A smoother sign-in code experience: no false error flash on a successful sign-in, no rapid-fire failures when correcting a wrong code, and tidier-looking banners.
- Sign-in no longer fails with a raw JSON error page when a user takes too long on the OTP step.
- Sign-in no longer hits a dead-end on the password form
- Client app developers:
Patch Changes
-
#130
6a8671dThanks @s-adamantine! - "Powered by Certified" footer now appears on every auth-service page.Affects: End users
End users: every page rendered by the auth service now displays the same "Powered by Certified" footer that the main sign-in already shows, so the branding is consistent end-to-end. New surfaces covered: the Account Settings sign-in flow at
/account/login(email-entry and code-entry steps), the "Choose your handle" page shown to new users after email verification, both Account Recovery steps (backup-email entry and recovery-code entry), the/accountsettings dashboard, the backup-email verification confirmation, the post-deletion confirmation, and the generic error pages used by 404 / 500 / session-expired flows. -
#141
899346cThanks @aspiers! - "Use a different account" on the chooser now reliably takes you to the email form, not the code step for the previous account.Affects: End users
End users: when you click "Another account" on the account chooser to sign in as someone else, you now always land on a fresh email entry form. Previously, if the app that started the sign-in had pre-filled an account hint, the page jumped straight to the verification-code step for the previous account — leaving you stuck typing a code for an account you were trying to leave.
-
#122
dacf1d2Thanks @aspiers! - Sign-in no longer fails with "Authentication session expired" when an OTP code is resent after the original code times out.Affects: End users
End users: Previously, if you took longer than 10 minutes to enter the one-time code emailed to you and then clicked Resend code, the new code would verify, but the next page would say "Authentication session expired. Please try again." and you would have to start the whole sign-in over. The OAuth session that was tracking your sign-in had the same 10-minute lifetime as the OTP code itself, so it had already gone away by the time the new code arrived.
The OAuth session now lives long enough to outlast a typical resend cycle, so a slow first attempt followed by Resend completes normally. The OTP code's own 10-minute lifetime is unchanged.
-
#127
8bf888bThanks @s-adamantine! - Terms of Use and Privacy Policy links on the sign-in page now open in a new tab.Affects: End users
End users: clicking Terms of Use or Privacy Policy on the sign-in page no longer navigates away from the in-progress sign-in. The links open in a new tab instead, so you can read the legal page and come back to finish signing in without restarting.
-
#136
143ff35Thanks @Kzoeps! - OAuth consent buttons stack cleanly on small screens.Affects: End users
End users: On phones and narrow browser windows, the consent screen now places the approve and deny buttons on separate lines so they are easier to read and tap. Larger screens keep the existing button layout.
-
#134
bce65b5Thanks @s-adamantine! - A smoother sign-in code experience: no false error flash on a successful sign-in, no rapid-fire failures when correcting a wrong code, and tidier-looking banners.Affects: End users, Client app developers
End users:
- A successful sign-in no longer briefly shows a red "Invalid OTP" message on its way to signing you in.
- After entering a wrong code, the boxes clear and focus jumps back to the first one, so retyping doesn't immediately resubmit the still-wrong code on every keystroke (which previously could lock you out for spamming the server).
- The red "Invalid OTP" and green "Code resent" banners are centred inside their coloured container instead of sitting in the corner of an empty wide box.
Client app developers: the sign-in page's flash-message container now uses a stable
flash-msgbase class witherror/successmodifier classes, so custom client CSS can restyle either variant cleanly via.flash-msg,.flash-msg.error, and.flash-msg.success. -
#128
0e62bd6Thanks @aspiers! - Sign-in no longer fails with a raw JSON error page when a user takes too long on the OTP step.Affects: End users
End users: Previously, if you took more than five minutes between requesting your one-time code and submitting it (a slow inbox, switching tabs, fishing the code out of spam, multiple Resend cycles), sign-in could fail with a blank page showing only
{"error": "Authentication failed"}on the PDS host — even though your OTP code itself was still valid. You now either land back inside the app you were signing into (which can offer a one-click retry), or see a styled error page on the PDS host explaining that sign-in timed out — depending on how far through the flow the timeout is detected. Either way, no more raw JSON. -
#129
14e5033Thanks @aspiers! - Sign-in no longer hits a dead-end on the password formAffects: End users
End users: if you saw a "handle and password" form during sign-in with no way to enter a code, that path is gone. The email-code form will be shown instead, and after entering the code you'll be signed in normally.
ePDS@0.6.0
Who should read this release
- End users:
- Auth-service login page can now offer ATProto/Bluesky handle sign-in alongside email OTP.
- Trusted apps can now show their own icon in the browser tab on the sign-in page.
- Refreshed sign-in page design, with new ways for apps to style it.
- Account settings page now shows your current handle.
- Sign-in, account, error, and OAuth-consent pages now show an icon in the browser tab, with separate assets for light and dark browser themes.
- Signing in once in your browser now works across all apps that use this ePDS.
- Account recovery via backup email now completes the OAuth flow instead of dropping users into signup.
- Visiting the bare auth service URL now takes you to the account page instead of a blank 404.
- Error pages on the sign-in service now match the rest of the signup and login look instead of showing plain default text, and apps calling the sign-in service now receive structured error responses by default instead of HTML pages.
- Client app developers:
- Auth-service login page can now offer ATProto/Bluesky handle sign-in alongside email OTP.
- Preview ePDS's auth-service screens and emails directly in your browser, without walking through the OAuth flow.
- Trusted apps can now show their own icon in the browser tab on the sign-in page.
- Trusted demo client now ships with a custom branded OTP email template.
- Refreshed sign-in page design, with new ways for apps to style it.
- Signing in once in your browser now works across all apps that use this ePDS.
- Security fix: client-supplied email templates now require the client to be on the trusted-clients list.
- Error pages on the sign-in service now match the rest of the signup and login look instead of showing plain default text, and apps calling the sign-in service now receive structured error responses by default instead of HTML pages.
- Operators:
- Auth-service login page can now offer ATProto/Bluesky handle sign-in alongside email OTP.
- Preview ePDS's auth-service screens and emails directly in your browser, without walking through the OAuth flow.
- Trusted apps can now show their own icon in the browser tab on the sign-in page.
- Trusted demo client now ships with a custom branded OTP email template.
- Refreshed sign-in page design, with new ways for apps to style it.
- Sign-in, account, error, and OAuth-consent pages now show an icon in the browser tab, with separate assets for light and dark browser themes.
- Signing in once in your browser now works across all apps that use this ePDS.
- Fix a pds-core crash on the account chooser (
/account) caused by response-rewrite middleware running after upstream had already flushed headers. - Security fix: client-supplied email templates now require the client to be on the trusted-clients list.
- Auth-service rate limiter can now be disabled for single-source-IP test environments.
- Account recovery via backup email now completes the OAuth flow instead of dropping users into signup.
- Visiting the bare auth service URL now takes you to the account page instead of a blank 404.
Minor Changes
-
#115
7f265b7Thanks @aspiers! - Auth-service login page can now offer ATProto/Bluesky handle sign-in alongside email OTP.
Affects: End users, Client app developers, Operators
End users:
- When the app you came from supports it, the sign-in page now shows an "Or sign in with ATProto/Bluesky" button under the email form.
- Clicking the button switches the form into handle-entry mode (e.g.
you.bsky.social). Submitting a handle takes you back to your own PDS to finish signing in there. - Clicking the button again returns you to the email form.
Client app developers: opt in by adding
epds_handle_login_urlto your OAuth client metadata.- The value must be an absolute https:// URL on your client's own origin. ePDS auth-service redirects the browser to that URL with
?handle=<value>appended when the user submits a handle. - Your route is responsible for resolving the handle to its PDS and starting a fresh OAuth flow against that PDS — auth-service is bound to one PDS and cannot start a PAR on your client's behalf, so off-PDS handles only work via this hand-off.
- The reference demo client opts in by exposing
${baseUrl}/api/oauth/login?handle=..., which already accepts ahandlequery parameter and resolves it dynamically. - If you do not declare
epds_handle_login_url, the button is not rendered. Existing clients see no behaviour change.
Operators: no new required configuration. The button only renders for OAuth clients that explicitly opt in via their metadata.
-
#103
226781b/ #93d363b3dThanks @aspiers! - Preview ePDS's auth-service screens and emails directly in your browser, without walking through the OAuth flow.
Affects: Client app developers, Operators
Client app developers:
A new preview route on pds-core renders the account chooser with fixture sessions and your branding CSS, alongside the existing
/preview/consentroute. Open/preview/chooser(linked from the/previewindex) to see how a returning user with one or more bound accounts will see your client. Inline controls on the index let you tweak the preview without editing the URL: a number field for?numAccounts=N(clamped to 1–10) grows or shrinks the fixture account list, and a dropdown for?epds_handle_mode=overrides the handle-picker mode the same way a real OAuth request can. The dropdown defaults to "Auto", which omits the param so client metadata (or the operator's env default) wins — exactly the production resolver order. The same?client_id=<URL-of-your-client-metadata.json>param the other preview routes accept also injects your branding CSS, subject to the standard trusted-clients gate. The existing/preview/choose-handlelink on the auth-service index gains the same?epds_handle_mode=and?error=dropdowns and collapses the four enumerated handle-mode entries into a single link with bound controls.Three new preview routes on the auth service render the exact email HTML real users receive, inside a sandboxed iframe:
/preview/emails/new-user— welcome / email-verification code sent during signup./preview/emails/returning-user— sign-in OTP sent when an existing user logs in to your app./preview/emails/recovery— backup-email verification link sent when a user adds a recovery address.
Each route accepts the same
?client_id=<URL-of-your-client-metadata.json>query param as the other preview pages, so you can see how your branded template will look without walking through a real OAuth flow. Optional extras:?otp=<code>to override the fixture OTP,?app=<name>to override the fixture app name on the returning-user template,?verify_url=<url>to override the bac...
ePDS@0.5.0
Who should read this release
- Client app developers & operators:
- End users of the trusted demo:
Minor Changes
-
#84
fe3ec90Thanks @aspiers! - Add preview routes on auth-service and pds-core for iterating on client branding CSS.Affects: Client app developers, Operators
Client app developers:
- Visit
/previewon either auth-service or pds-core for an index of every preview page. Each page renders against fixture data, so you can iterate on yourbranding.csswithout walking through a real OAuth flow. - Paste your
client-metadata.jsonURL into the input field on the index page. The value is persisted in your browser and wires up every preview link, subject to the samePDS_OAUTH_TRUSTED_CLIENTScheck as a real flow. Leave it blank to see the unbranded baseline. - The workflow becomes: edit
branding.css, refresh any preview page. No OTP emails, no full flow. - The demo app links directly to the auth-service preview index with its own
client_idpre-selected.
Operators:
- Two new env vars gate the preview routes, one per service:
AUTH_PREVIEW_ROUTES=1on auth-service,PDS_PREVIEW_ROUTES=1on pds-core. Both are independent. - Safe to enable on preview deployments (Railway PR previews,
pr-base, dev) and on local development instances. Preview routes don't affect real auth flows — they short-circuit real state — so they can technically run in production too, but they are a developer-only surface and are best left off outside preview/dev envs. - Privacy: enabling previews exposes
/preview/cache-status, which returns the list ofclient_idURLs currently in the shared client-metadata cache — i.e. apps that have recently started an OAuth flow against this PDS. That partially leaks which third-party clients are using the instance, so keep previews disabled in production unless you're comfortable with that. - See
packages/auth-service/.env.exampleandpackages/pds-core/.env.examplefor the full notes.
- Visit
Patch Changes
-
#83
cc722c4Thanks @aspiers! - Demo amber/ocean themes now colour the OAuth consent page correctly.Affects: End users of the trusted demo
End users: The consent screen shown after signing in via the trusted demo now uses the demo's own warm indigo / amber palette throughout — the Authorize and Deny-access buttons, the "Authorize" header strip, and the surrounding surface all match the theme instead of falling back to the default @atproto/oauth-provider dark-mode look.
The previous CSS targeted auth-service's hand-rolled login markup (
.btn-primary,.container,.field), which does not exist on the consent page — that page is built from@atproto/oauth-provider-ui, which is a Tailwind-utility bundle whose colours are driven by CSS custom properties (--branding-color-primaryand friends). The demo theme now overrides those variables at:root, so a single declaration recolours everybg-primary/text-primary/border-primaryutility on the consent page at once, and additionally paints the card surface and body background to match. -
#89
1942ebbThanks @aspiers! - Fix two preview-route cache bugs and remove long-stale debug endpoints.Affects: Client app developers, Operators
Client app developers:
- Preview-route fetch failures no longer poison the shared client-metadata cache. Previously, a failed preview fetch for a
client_idwith a valid 10-minute entry would overwrite that entry with a 60-second branding-less fallback, silently droppingbranding.csson real OAuth flows for up to a minute. The in-memory cache is now only written by real-flow resolution. - The auth-service HTML preview pages (
/preview/login,/preview/login-otp,/preview/choose-handle,/preview/choose-handle-picker,/preview/recovery,/preview/recovery-otp, and the/previewindex) now sendCache-Control: no-store. Without it, a browser refresh could serve a cached page and never ask the server for freshbranding.css, breaking the advertised "editbranding.css, refresh the preview page" workflow. /preview/validatenow flagsbranding.csswhose escaped size exceeds the 32 KB injection limit as an error, instead of reportingokand letting the developer discover later that their CSS was silently dropped on real OAuth flows. Byte counts now matchgetClientCss()'s measurement (escaped UTF-8).
Operators:
- Removed
/_internal/debug-grantsand/_internal/debug-recent-accounts. These were added as temporary HYPER-270 debugging endpoints with a code comment marking them for removal before PR #21 shipped (v0.2.2); they survived through v0.2.2, v0.3.0, v0.4.0, and the pending v0.5.0. The matching env varEPDS_DEBUG_GRANTSis no longer read.
- Preview-route fetch failures no longer poison the shared client-metadata cache. Previously, a failed preview fetch for a
ePDS@0.4.0
Who should read this release
- End users:
- Client app developers:
- Operators:
Minor Changes
-
#48
0c275e4Thanks @Kzoeps & @aspiers! - Trusted apps can now style the sign-in and consent pages to match their own brand.Affects: End users, Client app developers, Operators
End users: When signing in through an app that your ePDS operator has approved for branding, the login page, code entry page, handle picker, account recovery page, and consent page will display that app's colour scheme instead of the default look. The pages still work exactly the same way — only the visual appearance changes.
Client app developers: Add a
branding.cssfield inside abrandingobject in yourclient-metadata.json. The CSS is injected as a<style>tag into every auth-service page and the PDS stock consent page (/oauth/authorize) when yourclient_idis listed in the operator'sPDS_OAUTH_TRUSTED_CLIENTS. The CSS is size-capped at 32 KB (measured in escaped UTF-8 bytes) and sanitised to prevent</style>tag closure. The CSPstyle-srcdirective is updated with a SHA-256 hash of the injected CSS. Example metadata:{ "client_id": "https://app.example/client-metadata.json", "client_name": "My App", "branding": { "css": "body { background: #0f1b2d; color: #e2e8f0; } .btn-primary { background: #3b82f6; }" } }Untrusted clients (not in
PDS_OAUTH_TRUSTED_CLIENTS) never get CSS injection, regardless of what their metadata contains.Operators: CSS branding injection is controlled by the existing
PDS_OAUTH_TRUSTED_CLIENTSenv var on pds-core. No new env vars are required on pds-core or auth-service. The auth-service reads the samePDS_OAUTH_TRUSTED_CLIENTSlist to decide whether to inject CSS on its pages (login, OTP, choose-handle, recovery). Seedocs/configuration.mdfor the full reference.For the demo app, a new optional
EPDS_CLIENT_THEMEenv var selects a named theme preset (e.g.ocean) that applies consistent styling to both the demo's own pages and the CSS served in its client metadata. When unset, the demo uses the default light theme with no branding CSS. Seepackages/demo/.env.examplefor details.
Patch Changes
-
#77
b3c779aThanks @aspiers! - Generate ES256 keypairs withpnpm jwk:generateinstead of re-running full setup.Affects: Client app developers
Client app developers: A new
pnpm jwk:generatecommand outputs a compact ES256 private JWK (with auto-derivedkid) on stdout. Use this when you need a keypair forprivate_key_jwtclient authentication without running the fullscripts/setup.sh. The output is suitable for theEPDS_CLIENT_PRIVATE_JWKenvironment variable (used by the bundled demo app inpackages/demo, not by third-party client apps) or for embedding the public half in any client metadata'sjwksfield. -
#77
0eaded0Thanks @aspiers! - Updated login integration docs to recommend@atproto/oauth-client-nodeand confidential clients.Affects: Client app developers
Client app developers: The tutorial and skill reference now recommend
@atproto/oauth-client-node'sNodeOAuthClientfor Flow 2 (no hint, handle, or DID input), which handles PAR, PKCE, DPoP, and token exchange automatically. Flow 1 (emaillogin_hint) remains hand-rolled. The default client metadata example has been flipped from"token_endpoint_auth_method": "none"to"private_key_jwt"withjwks_urior inlinejwksfor publishing the public key. A new "Confidential vs public clients" section explains the trade-offs — notably that public clients force a consent screen on every login. New sections cover JWKS key generation, publishing, and rotation.
ePDS@0.3.0
Who should read this release
- Client app developers and Operators:
Minor Changes
-
#74
b46273aThanks @aspiers! - The health endpoint now reports the running ePDS version.Affects: Client app developers, Operators
Client app developers: both
/healthendpoints (pds-core and auth-service) now include aversionfield in their JSON response (e.g.{ "status": "ok", "service": "epds", "version": "0.2.2+f37823ee" }). You can use this to check which ePDS release your app is running against. The demo frontend also displays the version in its page footer.Operators: in Docker and Railway deployments the version is automatically set to
<package.json version>+<8-char commit SHA>at build time. In local dev it falls back to the rootpackage.jsonversion (e.g.0.2.2). To override, set theEPDS_VERSIONenvironment variable on both pds-core and auth-service to any string. Docker Compose users should now build withpnpm docker:buildinstead ofdocker compose builddirectly — the wrapper stamps the version before building, and the build will fail if the version stamp is missing.
Patch Changes
-
#76
f709066Thanks @aspiers! - The upstream PDS version now appears on the stock health endpoint.Affects: Client app developers, Operators
/xrpc/_healthnow returns the upstream@atproto/pdsversion in its JSON response (e.g.{ "version": "0.4.211" }). Previously this endpoint returned{}. This is independent of the ePDS version reported by/health.Operators: no configuration is needed — the version is read from the installed
@atproto/pdspackage at startup. To override, set thePDS_VERSIONenvironment variable on pds-core.