Skip to content

Upgrade wrapped atproto PDS to latest upstream (Node 22 + ESM) - #233

Open
aspiers wants to merge 12 commits into
mainfrom
feat/atproto-upstream-upgrade
Open

Upgrade wrapped atproto PDS to latest upstream (Node 22 + ESM)#233
aspiers wants to merge 12 commits into
mainfrom
feat/atproto-upstream-upgrade

Conversation

@aspiers

@aspiers aspiers commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Upgrades the wrapped atproto stack in pds-core to the latest upstream, gated behind a Node 22 + pure-ESM platform migration. Delivers beads epic atproto-pd5 (8 beads).

What changed

Package From To
@atproto/pds ^0.4.209 ^0.5.23
@atproto/oauth-provider ^0.15.9 ^0.21.1
@atproto/oauth-provider-ui ^0.4.3 ^0.8.9
engines.node >=20 >=22

Commits (one per bead, in dependency order):

  • build(pds-core): move to Node 22 + pure ESMtype: module, engines.node>=22, NodeNext, require.resolvecreateRequire(import.meta.url), Docker/CI node 20→22, .nvmrc.
  • build(pds-core): bump @atproto deps to latest upstream.
  • refactor(pds-core): repoint oauth-provider imports to 0.21 subpaths — 0.20 removed the barrel entrypoint; repointed to /errors, /constants, /store, /provider.
  • fix(pds-core): adopt strongly-typed Account did/handleAccount moved to @atproto/oauth-provider-api with sub/preferred_username → branded did/handle; added @atproto/syntax for isValid* guards.
  • fix(pds-core): match chooser enrichment to oauth-provider-ui 0.8 markup — aria-label/text copy changed; the "Another account" → auth-service rebind now matches new + legacy variants.
  • docs(changeset): atproto upstream upgrade + Node 22 requirement.

Two beads were no-code validations:

  • Reach-in re-validation — all 22 as any/as unknown as reach-ins (requestManager, clientManager, deviceManager.store, accountManager.*, ctx.accountManager.db.db) confirmed intact against the new upstream .d.ts at signature + field-shape level.
  • onSignInFailed reconcile — found not applicable: onSignInFailed fires on oauth-provider's password sign-in, which ePDS bypasses (passwordless). ePDS OTP logging is on the separate better-auth emailOTP path. No duplication; nothing to reconcile. (@atproto/pds also hardcodes its hooks object, so the hook is unreachable regardless.)

Validation (Node 22)

  • pnpm --recursive build — clean
  • pnpm typecheck — clean
  • pnpm test1084 passed / 73 files
  • Coverage above thresholds (stmts 58.4 / branch 57.6 / funcs 71.9 / lines 57.2)
  • pds-core/dist/index.js boots as ESM
  • e2e — deferred to this PR's CI

Operator note

Requires Node.js 22 (was 20). Docker images already use node:22-alpine. A native better-sqlite3 rebuild is needed on an in-place Node switch (pnpm rebuild better-sqlite3); a clean install on Node 22 handles it.

Out of scope: the constantTime log warning (unchanged upstream; silence separately).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added stronger validation for account identifiers, handles, and hosted-handle lookups.
    • Improved account selection and sign-in handling across current and legacy interface variants.
    • Added enhanced account-management support through the updated AT Protocol integration.
  • Bug Fixes

    • Improved account resolution and login-hint matching for more reliable authentication.
  • Compatibility

    • Upgraded to Node.js 22.19 or later.
    • Updated container images and AT Protocol packages.
    • Existing passwordless sign-in and email verification remain unchanged.

aspiers and others added 6 commits August 3, 2026 18:31
Platform prerequisite for the upstream @atproto upgrade (bead atproto-pd5.1).
Latest @atproto/pds and @atproto/oauth-provider drop Node 18/20 and convert
to pure ESM, so pds-core must flip first.

- pds-core: add "type":"module" and engines.node>=22; bump @types/node ^22
- root engines.node >=20.0.0 -> >=22; add .nvmrc (22)
- tsconfig: module/moduleResolution Node16 -> NodeNext
- index.ts: replace bare require.resolve with createRequire(import.meta.url)
- preview-shared.ts: createRequire(__filename) -> createRequire(import.meta.url);
  refresh stale CJS/Node-20 comments
- preview-test-helpers: annotate mockLogger return (TS2742 under NodeNext)
- Dockerfiles + CI workflows: node 20 -> 22

Dependency versions unchanged (bead atproto-pd5.2). Build clean, 299 pds-core
tests green, dist/index.js boots as ESM on Node 22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bead atproto-pd5.2. Raise the three atproto pins through the breaking
minor channels to the current latest:

- @atproto/oauth-provider  ^0.15.9  -> ^0.21.1
- @atproto/oauth-provider-ui ^0.4.3 -> ^0.8.9
- @atproto/pds             ^0.4.209 -> ^0.5.23

Upstream advanced past the upgrade brief (checked 2026-07-27): oauth-provider
crossed an additional 0.21.0 minor beyond the report's 0.20.x target — an
extra breaking channel to clear in bead .3/.4.

Install is clean on Node 22 with a single resolved copy of each package and
no old transitive versions. The build is intentionally RED after this commit:
the barrel-import and type breakage is fixed in beads .3 (imports) and .5
(Account typing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bead atproto-pd5.3. oauth-provider 0.20.0 removed the bare barrel entrypoint
(confirmed absent in 0.21.1: exports are ./errors ./constants ./store ./hooks
./provider ./verifier ./middleware ./utils). Repoint each pds-core import:

- HandleUnavailableError            -> @atproto/oauth-provider/errors
- DEVICE_ID_*/SESSION_ID_* consts   -> @atproto/oauth-provider/constants
- DeviceAccount, DeviceId (types)   -> @atproto/oauth-provider/store
- OAuthProvider (type)              -> @atproto/oauth-provider/provider

No BrandingInput/CustomizationInput references exist in pds-core, so no rename
needed. All 4 module-not-found (TS2307) errors are resolved. The remaining
build errors are strong did/handle typing + the changed Account shape, fixed
in bead atproto-pd5.5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bead atproto-pd5.5. oauth-provider-api 0.7 retyped Account: the OIDC-style
`sub`/`preferred_username` string fields became branded `did: DidString` and
`handle?: HandleString`, and pds/oauth-provider getAccount/createAccount now
require branded DidString/HandleString/AtIdentifierString rather than plain
strings.

- add @atproto/syntax ^0.7.2 (direct dep) for the isValid* type guards
- import Account type; drop the `let account: any` escape hatch — type it
  Account | undefined and narrow before use
- account.sub -> account.did at every read site
- brand handles via isValidHandle before createAccount / getAccount:
  - constructed chosen handle: invalid => 500 (would be a construction bug)
  - generated random handle: invalid => throw (retryable)
- brand lookup identifiers via isValidHandle / isValidAtIdentifier, preserving
  behaviour and improving one case:
  - /_internal/account-by-handle: invalid id => { email: null } (= not found)
  - /_internal/check-handle: invalid handle => { exists: true } so signup
    blocks it up front instead of failing later at account creation
  - hosted-domain resolution: invalid handle => 404 (= not found)
- auth-ui-guard login_hint match: account.sub/preferred_username ->
  account.did/handle; update the binding test fixture to the new shape

pds-core build is clean and all 299 tests pass on Node 22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bead atproto-pd5.6. oauth-provider-ui 0.4->0.8 restructured the account
chooser and changed its copy (strings are now runtime-resolved i18n message
ids, so the served DOM text changed):

- "Another account" rebind: the aria-label changed from
  "Login to account that is not listed" to
  "Sign in to an account that is not listed", and the visible text from
  "Another account" to "Select another account". Match either variant
  (aria-label list + text list) so the rebind survives this change and
  degrades rather than breaks on the next copy tweak. Verified both new
  strings are present in the installed 0.8.9 bundle.
- "Sign up" hide: 0.8 dropped the signup affordance from the account-
  selector page entirely (no "Sign up"/"Create account" string in the
  account-page bundle), so hideSignup() is now a no-op. Kept as a cheap
  guard and documented the change.

client-css-injection.ts needs no change: it keys off `</head>` and the CSP
style-src header, both markup-version-independent. The email-beside-handle
enrichment matches on handle/DID text content, not aria-labels, so it is
unaffected.

pds-core build clean, all 299 tests pass on Node 22 (chooser test updated to
assert the new + legacy selector variants).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bead atproto-pd5.8. Document the upstream bump (pds 0.5.23, oauth-provider
0.21.1, oauth-provider-ui 0.8.9) and the operator-facing Node 22 requirement
for the release changelog.

Full validation on Node 22: recursive build clean, typecheck clean, 1084
unit tests pass, coverage thresholds hold (stmts 58.4 / branch 57.6 / funcs
71.9 / lines 57.2), pds-core boots as ESM. e2e deferred to CI (needs a live
stack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 3, 2026 20:14
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview Aug 4, 2026 10:18am

Request Review

@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: aafe175

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49bb82d3-aad4-4061-96df-c1aacfcd534a

📥 Commits

Reviewing files that changed from the base of the PR and between cb59161 and aafe175.

📒 Files selected for processing (6)
  • .changeset/atproto-upstream-upgrade.md
  • .nvmrc
  • package.json
  • packages/pds-core/package.json
  • packages/pds-core/src/__tests__/identifier-guards.test.ts
  • packages/pds-core/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • .nvmrc
  • .changeset/atproto-upstream-upgrade.md
  • packages/pds-core/src/tests/identifier-guards.test.ts
  • packages/pds-core/package.json
  • package.json
  • packages/pds-core/src/index.ts

📝 Walkthrough

Walkthrough

The project upgrades its runtime baseline to Node.js 22.19.0, adopts ESM-compatible resolution, updates AT Protocol dependencies, and strengthens typed account and handle validation. OAuth account-switching logic and tests support updated upstream fields and labels.

Changes

Runtime and account upgrade

Layer / File(s) Summary
Node.js 22 runtime alignment
.changeset/..., .nvmrc, package.json, packages/pds-core/package.json, .github/workflows/*, Dockerfile.*
Development, CI, release, Docker, package engine, dependency, and changeset configuration now target Node.js 22.19.0 or newer.
ESM package and resolution migration
packages/pds-core/src/index.ts, packages/pds-core/src/auth-ui-guard.ts, packages/pds-core/src/lib/*, packages/pds-core/src/__tests__/preview-test-helpers.ts
Imports, package resolution, filesystem paths, manifest loading, device-account types, and logger mocks now use the updated ESM-compatible module layout.
Account identity and UI compatibility
packages/pds-core/src/lib/identifier-guards.ts, packages/pds-core/src/index.ts, packages/pds-core/src/auth-ui-guard.ts, packages/pds-core/src/chooser-enrichment.ts, packages/pds-core/src/__tests__/*
Account handling uses typed DIDs and handles. Identifier validation precedes lookup. Account-switcher matching supports current and legacy upstream fields, labels, and selectors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OAuthProvider
  participant PdsCore
  participant AccountStore
  OAuthProvider->>PdsCore: provide typed account identity
  PdsCore->>PdsCore: validate identifier and handle syntax
  PdsCore->>AccountStore: resolve or create account
  AccountStore-->>PdsCore: return account.did
  PdsCore-->>OAuthProvider: bind resolved account
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: the atproto upgrade, Node.js 22 requirement, and ESM migration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/atproto-upstream-upgrade

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.

@railway-app

railway-app Bot commented Aug 3, 2026

Copy link
Copy Markdown

🚅 Deployed to the ePDS-pr-233 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/pds-core ✅ Success (View Logs) Web Aug 4, 2026 at 10:18 am
@certified-app/auth-service ✅ Success (View Logs) Web Aug 3, 2026 at 11:58 pm
@certified-app/demo ✅ Success (View Logs) Web Aug 3, 2026 at 11:58 pm
@certified-app/demo untrusted ✅ Success (View Logs) Web Aug 3, 2026 at 11:58 pm

@coveralls-official

coveralls-official Bot commented Aug 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 30900021364

Coverage decreased (-0.3%) to 57.352%

Details

  • Coverage decreased (-0.3%) from the base build.
  • Patch coverage: 25 uncovered changes across 1 file (6 of 31 lines covered, 19.35%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
packages/pds-core/src/index.ts 25 0 0.0%
Total (4 files) 31 6 19.35%

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
packages/pds-core/src/index.ts 2 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 3121
Covered Lines: 1786
Line Coverage: 57.23%
Relevant Branches: 1939
Covered Branches: 1116
Branch Coverage: 57.56%
Branches in Coverage %: Yes
Coverage Strength: 9.31 hits per line

💛 - Coveralls

Copilot AI 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.

Pull request overview

This PR upgrades the wrapped upstream AT Protocol stack used by packages/pds-core (notably @atproto/pds and @atproto/oauth-provider*) and completes the accompanying platform migration to Node.js 22 with pure ESM semantics across pds-core, plus related CI/Docker updates.

Changes:

  • Bump upstream AT Protocol dependencies (@atproto/pds0.5.23, @atproto/oauth-provider0.21.1, @atproto/oauth-provider-ui0.8.9) and adapt imports/types to new subpaths + branded did/handle.
  • Migrate pds-core to pure ESM (NodeNext TS config, type: module, createRequire(import.meta.url) replacements).
  • Update infra to Node 22 (Docker images, CI workflows, .nvmrc) and add a changeset documenting the operator requirement.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks upgraded atproto dependency graph and new transitive deps for the Node 22 stack.
packages/pds-core/tsconfig.json Switches TS module settings to NodeNext for ESM correctness.
packages/pds-core/src/lib/preview-shared.ts Updates createRequire usage for ESM + keeps asset loading lazy.
packages/pds-core/src/lib/device-accounts.ts Repoints oauth-provider imports to new 0.21 subpaths.
packages/pds-core/src/index.ts Adapts startup/package resolution for ESM; updates account typing + handle validation guards; updates internal endpoints.
packages/pds-core/src/chooser-enrichment.ts Updates chooser DOM matching to oauth-provider-ui 0.8 copy/markup changes.
packages/pds-core/src/auth-ui-guard.ts Updates oauth-provider imports and account field names (subdid, preferred_usernamehandle).
packages/pds-core/src/tests/preview-test-helpers.ts Adds explicit return type to avoid TS2742 under NodeNext resolution.
packages/pds-core/src/tests/chooser-enrichment.test.ts Updates assertions to match the new chooser enrichment matching strategy.
packages/pds-core/src/tests/auth-ui-guard.test.ts Updates fixtures/predicates for the new strongly-typed account fields.
packages/pds-core/package.json Marks package as type: module, bumps deps, sets Node engine to 22.
package.json Raises repo-wide Node engine requirement to 22.
Dockerfile.pds Bumps base/runtime image to node:22-alpine.
Dockerfile.demo Bumps base/runtime image to node:22-alpine.
Dockerfile.auth Bumps base/runtime image to node:22-alpine.
.nvmrc Pins local dev Node major to 22.
.github/workflows/release.yml Updates release workflow Node version to 22.
.github/workflows/e2e-tests.yml Updates e2e workflow Node version to 22.
.github/workflows/ci.yml Updates CI workflows Node version to 22.
.changeset/atproto-upstream-upgrade.md Adds release note + operator guidance for the upstream upgrade and Node 22 requirement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json
Comment thread .nvmrc Outdated
Comment thread .changeset/atproto-upstream-upgrade.md Outdated
Comment thread packages/pds-core/package.json
The ESM migration missed a `__dirname` at the favicon/public-dir setup
(index.ts). `__dirname` is a CommonJS global and is undefined in pure ESM,
so the pds-core container crashed at startup with
`ReferenceError: __dirname is not defined` — after config parsing, which is
why the local docker boot-test (which aborted earlier on missing blobstore
config) and the vitest suite (vitest provides __dirname) both missed it. It
surfaced only on the Railway deploy, where full env let startup reach the
crash and the healthcheck then failed.

Derive the module directory from `fileURLToPath(import.meta.url)` instead.

Verified in-container on Node 22: the image now boots to "ePDS running"
(listening on :3000) with valid env; no __dirname reference remains in the
compiled dist. Build + 299 pds-core tests still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 21:07
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-233 August 3, 2026 21:07 Destroyed

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (3)

package.json:9

  • engines.node is set to >=22, but the lockfile now includes undici@8.10.0 with engines: {node: '>=22.19.0'}. This means installs/runs on Node 22.0–22.18 can fail (especially with engine-strict) even though the repo claims they’re supported. Consider bumping the minimum Node version to match the strictest transitive runtime requirement.
  "engines": {
    "node": ">=22"
  },

packages/pds-core/package.json:9

  • engines.node is set to >=22, but the lockfile pulls in undici@8.10.0 (via the upgraded atproto stack) which declares engines: {node: '>=22.19.0'}. To avoid runtime/installation failures on early Node 22 minors, align this package’s minimum Node version with the actual dependency requirements.
  "engines": {
    "node": ">=22"
  },

packages/pds-core/src/index.ts:1245

  • When domain isn’t a syntactically valid handle, the route returns 404 NotFound with a “not found” message. This is inconsistent with the earlier InvalidRequest responses for bad inputs, and it makes an invalid query param look like a valid-but-missing resource. Returning 400 InvalidRequest here would better reflect the input validation failure.
    if (!isValidHandle(domain)) {
      return res.status(404).json({
        error: 'NotFound',
        message: 'handle not found for this domain',
      })

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/pds-core/src/index.ts (1)

28-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move all static imports before module initialization.

@atproto/pds appears between node: imports, and the OAuth and syntax imports appear after atprotoPdsPkg is initialized. Group all Node built-ins first, then external packages, and initialize atprotoPdsPkg after the complete import block.

Proposed import layout
-import { fileURLToPath } from 'node:url'
-import { PDS, envToCfg, envToSecrets, readEnv } from '`@atproto/pds`'
 import { readFileSync } from 'node:fs'
 import { createRequire } from 'node:module'
+import { fileURLToPath } from 'node:url'
+import { PDS, envToCfg, envToSecrets, readEnv } from '`@atproto/pds`'
+import { HandleUnavailableError } from '`@atproto/oauth-provider/errors`'
+import type { Account } from '`@atproto/oauth-provider/store`'
+import {
+  isValidHandle,
+  isValidAtIdentifier,
+  type DidString,
+} from '`@atproto/syntax`'

 const atprotoPdsPkg = ...
-
-import { HandleUnavailableError } from '`@atproto/oauth-provider/errors`'
-import type { Account } from '`@atproto/oauth-provider/store`'
-import { ... } from '`@atproto/syntax`'

As per coding guidelines: “Order imports as: Node built-ins (with node: prefix), external packages, internal workspace packages (@certified-app/*), then local relative imports.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pds-core/src/index.ts` around lines 28 - 45, Reorder the imports in
the module so every node:-prefixed built-in import comes first, followed by all
external package imports including `@atproto/pds`, OAuth, and syntax symbols; move
the atprotoPdsPkg initialization block below the complete import section without
changing its behavior.

Source: Coding guidelines

packages/pds-core/src/__tests__/auth-ui-guard.test.ts (1)

781-785: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Model optional account.handle in the test fixture.

The production predicate accepts an optional account.handle, but binding requires a string and casts the field as required. This prevents tests from constructing a valid DID-only account. Change the parameter and returned type to handle?: string, then add a DID-only case.

Proposed fixture fix
-  function binding(did: string, handle: string) {
+  function binding(did: string, handle?: string) {
...
-    } as unknown as { account: { did: string; handle: string } }
+    } as unknown as { account: { did: string; handle?: string } }

Confirm that the installed @atproto/oauth-provider 0.21.1 type preserves this optional field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pds-core/src/__tests__/auth-ui-guard.test.ts` around lines 781 -
785, Update the test fixture function binding to accept an optional handle and
return an account type with handle?: string, avoiding a cast that marks it
required. Add a DID-only test case exercising the production predicate, and
verify the installed `@atproto/oauth-provider` 0.21.1 types preserve the optional
account.handle field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/pds-core/tsconfig.json`:
- Around line 4-5: Update the module and moduleResolution settings in
packages/pds-core/tsconfig.json from NodeNext to Node16 to comply with the
repository TypeScript configuration rule; only change the repository rule
instead if NodeNext is intentionally required.

---

Nitpick comments:
In `@packages/pds-core/src/__tests__/auth-ui-guard.test.ts`:
- Around line 781-785: Update the test fixture function binding to accept an
optional handle and return an account type with handle?: string, avoiding a cast
that marks it required. Add a DID-only test case exercising the production
predicate, and verify the installed `@atproto/oauth-provider` 0.21.1 types
preserve the optional account.handle field.

In `@packages/pds-core/src/index.ts`:
- Around line 28-45: Reorder the imports in the module so every node:-prefixed
built-in import comes first, followed by all external package imports including
`@atproto/pds`, OAuth, and syntax symbols; move the atprotoPdsPkg initialization
block below the complete import section without changing its behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 507d0b68-61ff-46ff-a894-767542b9c91d

📥 Commits

Reviewing files that changed from the base of the PR and between a42d20a and 21c5a77.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/atproto-upstream-upgrade.md
  • .github/workflows/ci.yml
  • .github/workflows/e2e-tests.yml
  • .github/workflows/release.yml
  • .nvmrc
  • Dockerfile.auth
  • Dockerfile.demo
  • Dockerfile.pds
  • package.json
  • packages/pds-core/package.json
  • packages/pds-core/src/__tests__/auth-ui-guard.test.ts
  • packages/pds-core/src/__tests__/chooser-enrichment.test.ts
  • packages/pds-core/src/__tests__/preview-test-helpers.ts
  • packages/pds-core/src/auth-ui-guard.ts
  • packages/pds-core/src/chooser-enrichment.ts
  • packages/pds-core/src/index.ts
  • packages/pds-core/src/lib/device-accounts.ts
  • packages/pds-core/src/lib/preview-shared.ts
  • packages/pds-core/tsconfig.json

Comment thread packages/pds-core/tsconfig.json Outdated
Recover the coverage the strong-typing change (bead .5) dropped: the branded
isValid* guards on the /_internal handle-lookup endpoints lived inline in the
untested main() and pulled overall coverage down.

Extract them into src/lib/identifier-guards.ts as type predicates
(canLookUpAccountByHandle / canCheckHandle / canResolveHandle) — following the
upstream-favicon.ts extract-and-test pattern — so the per-endpoint decision is
documented and unit-tested, while the `x is HandleString`/`AtIdentifierString`
predicate form preserves the narrowing getAccount() needs. Covers the
non-obvious case (an invalid handle on the availability check reads as taken,
not free) and the DID-vs-handle distinction between endpoints.

12 new tests; pds-core suite now 311 passing. Build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 22:34
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-233 August 3, 2026 22:34 Destroyed

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

packages/pds-core/src/tests/identifier-guards.test.ts:39

  • This test intends to verify that canCheckHandle() rejects DIDs even when they are syntactically valid, but did:plc:abc123 is probably not a valid PLC DID. Using a valid DID here makes the assertion meaningful and avoids the test passing/failing for the wrong reason.
  it('rejects a DID (this endpoint only accepts handles)', () => {
    // A DID is not a HandleString, so availability-check treats it as invalid
    // — which the endpoint reports as "taken", never "free".
    expect(canCheckHandle('did:plc:abc123')).toBe(false)
  })

Comment thread packages/pds-core/src/__tests__/identifier-guards.test.ts
Extend the identifier-guard extraction to the createAccount handle-branding
sites (chosen + random handle) via isValidConstructedHandle, moving the last
inline isValidHandle calls out of the untested main() and into the tested
lib/identifier-guards module. index.ts no longer imports isValidHandle
directly.

+4 tests (16 total in identifier-guards). Build clean, full suite 1100 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 23:52

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (3)

package.json:9

  • engines.node is set to >=22, but the lockfile includes runtime dependencies that declare engines: { node: '>=22.19.0' } (e.g. undici@8.10.0). To avoid advertising support for Node 22.0–22.18 (which may fail at install/runtime), bump the documented engine floor to match the strictest dependency.
  "engines": {
    "node": ">=22"
  },

packages/pds-core/package.json:9

  • This package declares engines.node: >=22, but the upgraded atproto dependency tree pulls in packages that require Node >=22.19.0 (e.g. undici@8.10.0 via @atproto-labs/fetch-node). To prevent users running earlier Node 22 minors, align this engine constraint with the strictest transitive requirement.
  "engines": {
    "node": ">=22"
  },

packages/pds-core/src/auth-ui-guard.ts:387

  • The JSDoc for filterCandidateBindings still refers to legacy sub / preferred_username fields, but the implementation now matches account.did / account.handle. This makes the comment misleading for future maintenance—update it to the new field names to match upstream oauth-provider-api 0.7 typing.
    // Account is now strongly typed (oauth-provider-api 0.7): sub -> did,
    // preferred_username -> handle (handle is optional).
    ({ account }) => account.did === loginHint || account.handle === loginHint,
  )

Address PR review (Copilot, CodeRabbit):

- engines.node >=22 -> >=22.19.0 (root + pds-core) and .nvmrc 22 -> 22.19.0:
  the lockfile pulls undici@8.10.0 (via @atproto) which declares
  engines.node >=22.19.0, so >=22 would allow installs on 22.0-22.18 that
  break at runtime. Changeset updated to state the 22.19 floor and why.
- pds-core tsconfig module/moduleResolution NodeNext -> Node16, matching the
  repo convention (root, auth-service, shared all use Node16). Node16 fully
  supports the pure-ESM build; verified tsc --build clean.

Build + typecheck clean, 315 pds-core tests pass on Node 22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address CodeRabbit review: the `atprotoPdsPkg` version-read sat between the
`node:` import group and the external `@atproto/*` imports. Move the two
`node:` imports (fs, module) up with the other built-ins, keep all static
imports together, and relocate the `atprotoPdsPkg` initialization below the
import block. `dotenv.config()` / `applyPdsPortFallback()` stay at the top by
design (they must run before readEnv, per the existing comments).

No behaviour change; build + typecheck clean, all pds-core tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-233 August 3, 2026 23:59 Destroyed
@aspiers

aspiers commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

(reply generated by Claude Opus via Claude Code)

Addressing the CodeRabbit review-summary nitpick on packages/pds-core/src/index.ts (28-45, "Move all static imports before module initialization"):

Fixed in 5e4070d. Moved the node:fs / node:module imports up with the other built-ins, grouped all static imports together, and relocated the atprotoPdsPkg version-read below the import block. (dotenv.config() and applyPdsPortFallback() stay at the very top by design — they must run before readEnv(), per the existing comments.) No behaviour change; build + typecheck clean, all pds-core tests pass.

The other summary nitpick (the did:plc:abc123 fixture) is answered on the inline thread — in short, @atproto/syntax accepts it as a valid DID, so the test is correct; left that thread open for human confirmation.

Copilot AI 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.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/pds-core/src/lib/identifier-guards.ts:32

  • canLookUpAccountByHandle accepts both handles and DIDs (it delegates to isValidAtIdentifier), so the name is misleading and makes call sites harder to understand. Consider renaming to something identifier-focused (e.g. canLookUpAccountByIdentifier / canLookUpAccountByAtIdentifier) and updating the import/uses accordingly.
export function canLookUpAccountByHandle(
  identifier: string,
): identifier is AtIdentifierString {
  return isValidAtIdentifier(identifier)
}

packages/pds-core/package.json:20

  • @atproto/crypto is listed as a direct dependency, but there are no references to it anywhere in packages/pds-core/src (and the lockfile already pulls in newer @atproto/crypto transitively via @atproto/pds). Keeping this unused direct dependency increases install surface and risks multiple @atproto/crypto versions in the tree. Remove it if it's not intentionally pinned/used.
  "dependencies": {
    "@atproto/crypto": "^0.4.5",
    "@atproto/oauth-provider": "^0.21.1",
    "@atproto/oauth-provider-ui": "^0.8.9",
    "@atproto/pds": "^0.5.23",
    "@atproto/syntax": "^0.7.2",

Copilot AI review requested due to automatic review settings August 4, 2026 00:00

Copilot AI 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.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/pds-core/src/index.ts:481

  • res.status(500).send('Account resolution failed') returns plain text, but other error paths in this callback use renderError(...) HTML. For consistency (and to ensure the browser gets an HTML page), return the same rendered error here too.
        res.status(500).send('Account resolution failed')

packages/pds-core/src/lib/identifier-guards.ts:2

  • The header comment says these guards are for the /_internal handle-lookup endpoints, but canResolveHandle() is also used by the public /tls-check route (Caddy on-demand TLS). Update the comment so it accurately reflects where these guards are used.
 * Branded-identifier guards for the `/_internal` handle-lookup endpoints.

packages/pds-core/src/index.ts:358

  • res.status(500).send('Invalid handle') returns a plain-text 500 while other failure paths in this handler return the HTML error page via renderError(...). Using the same HTML response here keeps content-type and user experience consistent.

This issue also appears on line 481 of the same file.

          res.status(500).send('Invalid handle')

packages/pds-core/package.json:16

  • @atproto/crypto is listed as a direct dependency of @certified-app/pds-core, but it doesn't appear to be imported/used anywhere in this package. If it's no longer needed after the upstream upgrade, removing it would avoid pinning an extra unused version in the lockfile.
    "@atproto/crypto": "^0.4.5",

Comment thread .changeset/atproto-upstream-upgrade.md Outdated
Comment thread packages/pds-core/src/__tests__/identifier-guards.test.ts Outdated
…ail claim

Address review (aspiers):

- identifier-guards.test.ts: use a proper `did:plc:` value
  (did:plc:7iza6de2dwap2sbkpav7c6c6) instead of the abc123 placeholder at both
  fixture sites. Still a valid DID per @atproto/syntax, so the assertions are
  unchanged — just realistic.
- changeset: email verification is already provided by ePDS's auth-service, so
  it is not an upstream gain. Reframe it as unchanged (alongside OTP sign-in)
  rather than listing it as new; keep handle change + deactivate/delete, which
  the upstream account-manager UI genuinely adds.

Build + 315 pds-core tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 4, 2026 10:17
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

Copilot AI 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.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (5)

.github/workflows/ci.yml:35

  • Same as the format job: since engines.node is >=22.19.0, pin this workflow job to 22.19.0 (or include it in a version matrix) so the minimum supported runtime is tested in CI.
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

.github/workflows/ci.yml:47

  • Same as other CI jobs: consider pinning node-version to 22.19.0 (or adding a matrix including it) to ensure the declared minimum supported Node version stays valid.
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

.github/workflows/ci.yml:59

  • Same as other CI jobs: to validate the stated support floor (engines.node >=22.19.0), pin this job’s Node version to 22.19.0 (or include it in a version matrix) rather than tracking the latest 22.x.
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

packages/pds-core/package.json:17

  • @atproto/crypto is listed as a direct dependency of @certified-app/pds-core, but there are no imports of it anywhere in this package. Keeping it here pins an older @atproto/crypto@0.4.x alongside the upgraded upstream stack (which pulls @atproto/crypto@0.5.x transitively), increasing duplication and making future upgrades harder to reason about. Remove the unused direct dependency (or bump it to match upstream if it’s intentionally required).
  "dependencies": {
    "@atproto/crypto": "^0.4.5",
    "@atproto/oauth-provider": "^0.21.1",

.github/workflows/ci.yml:23

  • The repo now declares engines.node >=22.19.0, but CI uses node-version: 22 (latest 22.x). That won’t exercise the minimum supported Node version and could allow accidental use of APIs introduced after 22.19.0. Consider pinning CI to 22.19.0 (or adding a matrix that includes the minimum) so compatibility with the stated floor is continuously verified.

This issue also appears in the following locations of the same file:

  • line 32
  • line 44
  • line 56
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

@aspiers
aspiers requested a review from Ashex August 4, 2026 10:25
aspiers added a commit that referenced this pull request Aug 4, 2026
…as errors

Three review points from PR #234.

Add an optional trailing argument to the backfill that scopes the run
to addresses containing it, case-insensitively -- "@gmail.com" for a
domain, a full address for one account. Operators can then work
through a deployment in batches or repair a single user instead of
being forced to process the whole table in one go. No argument still
means every account: "no filter" must never be read as "match
nothing", or a mistyped invocation would silently do nothing and look
like a clean run. The report names the filter, because otherwise
"0 account(s)" cannot be distinguished between nothing-left-to-do and
your-filter-matched-nothing.

Raise the failed-confirmation log from warn to error. Swallowing the
exception keeps the user signed in, but the account is left claiming
an unverified address to every relying party until some later sign-in
happens to succeed, and the operator has no other signal it occurred.
Self-healing is not the same as harmless, so it belongs at
error-level alerting.

Add End users to the changeset's audience list. The claim is
user-visible: once #233 lands, the PDS /account route stops telling
people to verify an address they have already confirmed.

Verified against a real account.sqlite built by upstream's migrator:
the domain filter matched mixed-case addresses, left non-matching
accounts untouched, a later unfiltered run picked up the remainder,
and no email_token rows were left behind.
aspiers added a commit that referenced this pull request Aug 4, 2026
…as errors

Three review points from PR #234.

Add an optional trailing argument to the backfill that scopes the run
to addresses containing it, case-insensitively -- "@gmail.com" for a
domain, a full address for one account. Operators can then work
through a deployment in batches or repair a single user instead of
being forced to process the whole table in one go. No argument still
means every account: "no filter" must never be read as "match
nothing", or a mistyped invocation would silently do nothing and look
like a clean run. The report names the filter, because otherwise
"0 account(s)" cannot be distinguished between nothing-left-to-do and
your-filter-matched-nothing.

Raise the failed-confirmation log from warn to error. Swallowing the
exception keeps the user signed in, but the account is left claiming
an unverified address to every relying party until some later sign-in
happens to succeed, and the operator has no other signal it occurred.
Self-healing is not the same as harmless, so it belongs at
error-level alerting.

Add End users to the changeset's audience list. The claim is
user-visible: once #233 lands, the PDS /account route stops telling
people to verify an address they have already confirmed.

Verified against a real account.sqlite built by upstream's migrator:
the domain filter matched mixed-case addresses, left non-matching
accounts untouched, a later unfiltered run picked up the remainder,
and no email_token rows were left behind.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants