Skip to content

ENSNode GraphQL API: Ergonomic Permissions#1745

Merged
shrugs merged 10 commits intomainfrom
feat/ergonomic-permissions
Mar 12, 2026
Merged

ENSNode GraphQL API: Ergonomic Permissions#1745
shrugs merged 10 commits intomainfrom
feat/ergonomic-permissions

Conversation

@shrugs
Copy link
Collaborator

@shrugs shrugs commented Mar 10, 2026

closes #1676

Copilot AI review requested due to automatic review settings March 10, 2026 02:16
@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

⚠️ No Changeset found

Latest commit: 4279823

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@vercel
Copy link
Contributor

vercel bot commented Mar 10, 2026

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

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Mar 12, 2026 6:52pm
ensnode.io Ready Ready Preview, Comment Mar 12, 2026 6:52pm
ensrainbow.io Ready Ready Preview, Comment Mar 12, 2026 6:52pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Adds domain-level permissions connection and filtering, consolidates permission types to PermissionsUserRef, narrows SQL selects to permissions_user columns, exposes new ID scalar types, removes two legacy permission modules, and tweaks lazy-connection memoization and pagination arg imports.

Changes

Cohort / File(s) Summary
Domain permissions
apps/ensapi/src/graphql-api/schema/domain.ts
Adds DomainPermissionsWhereInput and a permissions connection on ENSv2Domain returning PermissionsUserRef; resolver scopes by registry (chainId, address, resource) with optional user, supports totalCount and cursor pagination.
Account permissions wiring
apps/ensapi/src/graphql-api/schema.ts, apps/ensapi/src/graphql-api/schema/account.ts
Removed legacy module imports; Account.registryPermissions and Account.resolverPermissions now return PermissionsUserRef / related concrete types and select only schema.permissionsUser columns via getTableColumns, removing post-join row merging.
New concrete permission types
apps/ensapi/src/graphql-api/schema/registry-permissions-user.ts, apps/ensapi/src/graphql-api/schema/resolver-permissions-user.ts
Adds RegistryPermissionsUserRef and ResolverPermissionsUserRef object types exposing id, registry/resolver relation, resource, user, and roles fields resolved from permissions_user rows.
Removed legacy permission types
apps/ensapi/src/graphql-api/schema/account-registries-permissions.ts, apps/ensapi/src/graphql-api/schema/account-resolver-permissions.ts
Deleted AccountRegistryPermissionsRef and AccountResolverPermissionsRef modules and their exports/implementations.
Permissions schema adjustments
apps/ensapi/src/graphql-api/schema/permissions.ts, apps/ensapi/src/graphql-api/schema/account.ts
Changes id fields to typed scalar IDs (PermissionsId, PermissionsResourceId, PermissionsUserId) and adds non-null contract field on PermissionsResourceRef/PermissionsUserRef.
ID scalars & builder
apps/ensapi/src/graphql-api/builder.ts, apps/ensapi/src/graphql-api/schema/scalars.ts
Adds new scalar mappings and implementations for PermissionsId, PermissionsResourceId, PermissionsUserId, RegistrationId, RenewalId, ResolverRecordsId and exposes them in SchemaBuilder.
Small typings / fields updates
apps/ensapi/src/graphql-api/schema/registration.ts, apps/ensapi/src/graphql-api/schema/renewal.ts, apps/ensapi/src/graphql-api/schema/resolver-records.ts
Switches several .id fields to the new typed ID scalars (RegistrationId, RenewalId, ResolverRecordsId).
Pagination & lazy connection
apps/ensapi/src/graphql-api/lib/lazy-connection.ts, imports across schema files
Adjusted lazyConnection memoization check (null handling) and updated imports to include paginateBy and new paginated connection arg constants.
Schema import cleanup
apps/ensapi/src/graphql-api/schema.ts
Removed imports for deleted permission modules so they are no longer loaded during schema construction.

Sequence Diagram

sequenceDiagram
    participant Client as GraphQL Client
    participant Domain as ENSv2Domain Resolver
    participant Registry as Registry Loader
    participant DB as Permissions DB Query
    participant Pager as Cursor Pagination

    Client->>Domain: query ENSv2Domain.permissions(where?)
    Domain->>Registry: load registry for domain (chainId,address)
    Registry-->>Domain: registry info
    Domain->>DB: query permissions_user (chainId,address,resource[,user])
    DB-->>Domain: rows + totalCount
    Domain->>Pager: build cursor-paginated connection (edges, pageInfo, totalCount)
    Pager-->>Client: return PermissionsUserRef connection
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I nibbled through joins and trimmed the rows so neat,
Registry found, user filters set — permissions meet.
New IDs hop in line, scalars shiny and spry,
Pages turn, cursors dance, edges leap by.
Hooray — I thump, the graph grows ever sweet!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains a link to issue #1676 and lacks required sections (Summary, Why, Testing, Notes) specified in the template. Complete the PR description with Summary (what changed), Why (reasoning/context), Testing (how tested), and optional Notes sections following the template structure.
Out of Scope Changes check ⚠️ Warning The PR includes out-of-scope changes: removal of AccountRegistryPermissionsRef and AccountResolverPermissionsRef files, addition of new semantic permission types (RegistryPermissionsUserRef, ResolverPermissionsUserRef), and various ID type scalar updates (PermissionsId, RegistrationId, RenewalId, ResolverRecordsId). Clarify whether the deletion of account-registries-permissions.ts and account-resolver-permissions.ts files align with issue #1676 requirements. Explain why additional ID scalar types were added beyond permissions-related changes.
Linked Issues check ❓ Inconclusive The PR implements most objectives from #1676: Domain.registryRoles, Domain.resolverRoles with user filtering, Account.registryPermissions/resolverPermissions, and PermissionsUserWhereInput. However, it adds Domain.permissions and replaces expected field names with new semantic types (RegistryPermissionsUserRef/ResolverPermissionsUserRef). Clarify whether Domain.registryRoles/resolverRoles were renamed to Domain.permissions or if both should exist. Verify if RegistryPermissionsUserRef/ResolverPermissionsUserRef are the intended semantic replacements for PermissionsUserRef.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ENSNode GraphQL API: Ergonomic Permissions' clearly and concisely summarizes the main change of exposing permissions features in the GraphQL API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ergonomic-permissions
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an ENSv2Domain-level GraphQL connection to list permission-user entries (“roles”) scoped to the domain’s registry resource, with an optional user filter, to support more ergonomic permissions access patterns in ENSNode’s GraphQL API.

Changes:

  • Add ENSv2Domain.roles connection that queries permissions_users scoped by (chainId, address, resource=tokenId) with cursor pagination.
  • Introduce PermissionsUserWhereInput (currently with user: Address) for optional filtering.
  • Extend imports to use paginateBy and ID_PAGINATED_CONNECTION_ARGS for ID-based pagination.

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

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/schema/domain.ts`:
- Around line 375-379: PermissionsUserWhereInput is missing a description while
other input types have one; update the builder.inputType call for
PermissionsUserWhereInput to include a descriptive "description" property
(matching the style/format used by
DomainIdInput/DomainsWhereInput/AccountDomainsWhereInput) so the input type has
a clear description in the schema.
- Around line 342-343: The error thrown when a registry lookup fails uses an
unhelpful message ("never"); update the failing branch after
RegistryRef.getDataloader(context).load(parent.registryId) to throw a
descriptive Error that includes context (e.g., the missing registryId and
optionally the parent/domain id or name) so logs show which registry lookup
failed; locate the check using RegistryRef.getDataloader and parent.registryId
in domain.ts and replace the throw new Error("never") with a clear message like
"Registry not found for registryId=<...> (domainId=<...>)".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47b00f16-7a4c-450d-b66c-accdce9d4f2c

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa8326 and 0a0e01f.

📒 Files selected for processing (1)
  • apps/ensapi/src/graphql-api/schema/domain.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ensapi/src/graphql-api/schema/account.ts (1)

130-162: ⚠️ Potential issue | 🟠 Major

These connections lost the contract identity clients need.

PermissionsUserRef only exposes id, resource, user, and roles in apps/ensapi/src/graphql-api/schema/permissions.ts:196-239. After switching registryPermissions and resolverPermissions to that type, callers can no longer tell which registry or resolver each permission came from, so entries from different contracts become indistinguishable whenever resource values collide. Keep a contract-scoped wrapper type here, or expose the contract on PermissionsUser before changing these return types.

Also applies to: 167-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/graphql-api/schema/account.ts` around lines 130 - 162,
registryPermissions (and similarly resolverPermissions) now return
PermissionsUserRef which lacks contract identity, making permissions from
different contracts indistinguishable; restore contract-scoped identity by
either creating and returning a wrapper type (e.g., RegistryPermissionsUserRef
or ResolverPermissionsUserRef) that includes the contract identifier
(registry.chainId and registry.address) alongside the PermissionsUser fields, or
add contract fields to PermissionsUserRef itself; update the GraphQL type
declarations (where PermissionsUserRef is defined) and adjust the resolver in
registryPermissions/resolverPermissions to populate the new contract fields from
schema.registry (use the same join variables already present) so callers can
tell which contract each permission row belongs to.
♻️ Duplicate comments (2)
apps/ensapi/src/graphql-api/schema/domain.ts (2)

342-343: ⚠️ Potential issue | 🟡 Minor

Replace the "never" invariant message.

If this branch fires, the current error gives no registry or domain context for debugging.

♻️ Proposed fix
         const registry = await RegistryRef.getDataloader(context).load(parent.registryId);
-        if (!registry) throw new Error("never");
+        if (!registry) {
+          throw new Error(
+            `Invariant(ENSv2Domain.roles): Registry not found for registryId=${parent.registryId} domainId=${parent.id}`,
+          );
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/graphql-api/schema/domain.ts` around lines 342 - 343, The
invariant error "never" in the
RegistryRef.getDataloader(...).load(parent.registryId) branch is unhelpful;
update the thrown Error in that if (!registry) block to include useful context
such as parent.registryId and parent.name (or parent.id/domain identifier) and a
short message like "Registry not found" so logs/errors show which
registry/domain failed; locate the check in the resolver where
RegistryRef.getDataloader and parent.registryId are used and replace the generic
message with a formatted one containing those identifiers.

375-379: ⚠️ Potential issue | 🟡 Minor

Add a description to PermissionsUserWhereInput.

This is the only nearby input type without schema docs, so it sticks out in introspection.

♻️ Proposed fix
 export const PermissionsUserWhereInput = builder.inputType("PermissionsUserWhereInput", {
+  description: "Filter for permissions user queries.",
   fields: (t) => ({
     user: t.field({ type: "Address" }),
   }),
 });
As per coding guidelines, "Maintain comment consistency within a file; if most types, schemas, or declarations lack comments, do not add a comment to a single one."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/graphql-api/schema/domain.ts` around lines 375 - 379,
PermissionsUserWhereInput is missing a schema description while the surrounding
input types have docs; add a short description to the input type (for example
"Filter permissions by user address") by supplying the description option on the
builder.inputType call for PermissionsUserWhereInput and ensure the field user
remains of type "Address"—if the rest of the file actually lacks descriptions,
instead leave it unchanged to preserve consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/schema/domain.ts`:
- Around line 331-367: Replace the single roles field with two fields
registryRoles and resolverRoles (or add them alongside roles) so the schema
matches issue `#1676`: implement registryRoles using the same logic as the
existing resolve block (use
RegistryRef.getDataloader(context).load(parent.registryId), build scope with
eq(schema.permissionsUser.chainId, registry.chainId),
eq(schema.permissionsUser.address, registry.address),
eq(schema.permissionsUser.resource, parent.tokenId), and optional
args.where.user), and return lazyConnection with totalCount using
db.$count(schema.permissionsUser, scope) and connection using
resolveCursorConnection + paginateBy/orderPaginationBy as in the current code;
implement resolverRoles similarly but derive the resolver identifier from the
Domain parent object (e.g., parent.resolverId / parent.resolverAddress — use the
actual field on Domain), build a scope that filters permissions by that resolver
address/identifier (and chainId) and the same resource/tokenId, and reuse
lazyConnection + resolveCursorConnection pagination logic so clients can query
resolver-backed permissions separately from registry-backed permissions.

---

Outside diff comments:
In `@apps/ensapi/src/graphql-api/schema/account.ts`:
- Around line 130-162: registryPermissions (and similarly resolverPermissions)
now return PermissionsUserRef which lacks contract identity, making permissions
from different contracts indistinguishable; restore contract-scoped identity by
either creating and returning a wrapper type (e.g., RegistryPermissionsUserRef
or ResolverPermissionsUserRef) that includes the contract identifier
(registry.chainId and registry.address) alongside the PermissionsUser fields, or
add contract fields to PermissionsUserRef itself; update the GraphQL type
declarations (where PermissionsUserRef is defined) and adjust the resolver in
registryPermissions/resolverPermissions to populate the new contract fields from
schema.registry (use the same join variables already present) so callers can
tell which contract each permission row belongs to.

---

Duplicate comments:
In `@apps/ensapi/src/graphql-api/schema/domain.ts`:
- Around line 342-343: The invariant error "never" in the
RegistryRef.getDataloader(...).load(parent.registryId) branch is unhelpful;
update the thrown Error in that if (!registry) block to include useful context
such as parent.registryId and parent.name (or parent.id/domain identifier) and a
short message like "Registry not found" so logs/errors show which
registry/domain failed; locate the check in the resolver where
RegistryRef.getDataloader and parent.registryId are used and replace the generic
message with a formatted one containing those identifiers.
- Around line 375-379: PermissionsUserWhereInput is missing a schema description
while the surrounding input types have docs; add a short description to the
input type (for example "Filter permissions by user address") by supplying the
description option on the builder.inputType call for PermissionsUserWhereInput
and ensure the field user remains of type "Address"—if the rest of the file
actually lacks descriptions, instead leave it unchanged to preserve consistency.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 43307fe7-f4c2-4bed-99bd-dde810806a7a

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0e01f and 93f1e2d.

📒 Files selected for processing (5)
  • apps/ensapi/src/graphql-api/lib/lazy-connection.ts
  • apps/ensapi/src/graphql-api/schema/account-registries-permissions.ts
  • apps/ensapi/src/graphql-api/schema/account-resolver-permissions.ts
  • apps/ensapi/src/graphql-api/schema/account.ts
  • apps/ensapi/src/graphql-api/schema/domain.ts
💤 Files with no reviewable changes (2)
  • apps/ensapi/src/graphql-api/schema/account-resolver-permissions.ts
  • apps/ensapi/src/graphql-api/schema/account-registries-permissions.ts

Copilot AI review requested due to automatic review settings March 11, 2026 18:05
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 11, 2026 18:05 Inactive
@shrugs shrugs marked this pull request as ready for review March 11, 2026 18:05
@shrugs shrugs requested a review from a team as a code owner March 11, 2026 18:05
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 11, 2026 18:05 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 11, 2026 18:05 Inactive
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR refactors the ENSNode GraphQL API permissions layer to be more ergonomic: it replaces composite DTO wrapper types (AccountRegistryPermissions, AccountResolverPermissions) with leaner Pothos objectRef types (RegistryPermissionsUser, ResolverPermissionsUser) that resolve directly against the permissionsUser table row, adds a contract field to PermissionsResourceRef and PermissionsUserRef so callers can always identify the originating contract, introduces a new ENSv2Domain.permissions connection scoped by tokenId and registry join, and renames types/files to be semantically cleaner.

Key changes:

  • PermissionsUser and PermissionsResource now expose a contract field (AccountIdRef) so the originating contract is always navigable
  • Account.registryPermissions / Account.resolverPermissions now return the new RegistryPermissionsUser / ResolverPermissionsUser types rather than composite DTOs, simplifying the DB query to getTableColumns(schema.permissionsUser)
  • New ENSv2Domain.permissions connection lets callers query which users hold roles for a specific domain's token, with an optional where.user address filter
  • DomainPermissionsWhereInput replaces the more generically named input type flagged in earlier review threads
  • lazy-connection.ts receives a minor cosmetic cleanup (two-line null check collapsed to one)

Confidence Score: 4/5

  • PR is safe to merge; the refactor is well-structured with one minor API completeness gap in the new specialized permission types.
  • The logic is correct and the new query patterns mirror existing, proven patterns in the codebase. The only notable gap is that RegistryPermissionsUser and ResolverPermissionsUser omit the id and user fields that the underlying row carries and that the base PermissionsUserRef exposes — this can cause GraphQL client cache normalization issues but does not affect correctness of the data returned.
  • apps/ensapi/src/graphql-api/schema/registry-permissions-user.ts and apps/ensapi/src/graphql-api/schema/resolver-permissions-user.ts — both are missing id and user field declarations.

Important Files Changed

Filename Overview
apps/ensapi/src/graphql-api/schema/account.ts Replaces composite DTO types with the new RegistryPermissionsUserRef / ResolverPermissionsUserRef; queries simplified via getTableColumns; permissions description updated with optional contract filtering.
apps/ensapi/src/graphql-api/schema/domain.ts Adds ENSv2Domain.permissions connection (filtered by tokenId + registry join) and corresponding DomainPermissionsWhereInput; connection logic mirrors existing patterns correctly.
apps/ensapi/src/graphql-api/schema/permissions.ts Adds contract field to both PermissionsResourceRef and PermissionsUserRef, exposing chainId/address via AccountIdRef — addresses the previously noted missing contract context.
apps/ensapi/src/graphql-api/schema/registry-permissions-user.ts New type wrapping permissionsUser with a semantic registry field; missing id and user fields present in the raw row and exposed by the base PermissionsUserRef.
apps/ensapi/src/graphql-api/schema/resolver-permissions-user.ts Renamed/refactored from account-resolver-permissions.ts; same pattern as RegistryPermissionsUserRef — missing id and user fields.

Sequence Diagram

sequenceDiagram
    participant Client
    participant GraphQL
    participant DB

    Note over Client,DB: Account.registryPermissions
    Client->>GraphQL: account { registryPermissions { edges { node { registry { id } resource roles } } } }
    GraphQL->>DB: SELECT permissionsUser.* FROM permissionsUser INNER JOIN registry ON (chainId/address match) WHERE user = $account ORDER BY id
    DB-->>GraphQL: permissionsUser rows
    GraphQL-->>Client: RegistryPermissionsUser[] (registry resolved via makeRegistryId)

    Note over Client,DB: ENSv2Domain.permissions (new)
    Client->>GraphQL: domain { permissions(where: { user: $addr }) { edges { node { user contract resource roles } } } }
    GraphQL->>DB: SELECT permissionsUser.* FROM permissionsUser INNER JOIN registry ON (chainId/address match AND registry.id = $registryId) WHERE resource = $tokenId [AND user = $addr]
    DB-->>GraphQL: permissionsUser rows
    GraphQL-->>Client: PermissionsUser[] (with id, contract, user, resource, roles)

    Note over Client,DB: Account.permissions (optional contract filter)
    Client->>GraphQL: account { permissions(in: { chainId: 1, address: "0x..." }) { edges { node { contract { chainId address } } } } }
    GraphQL->>DB: SELECT * FROM permissionsUser WHERE user = $account AND chainId = $chainId AND address = $address
    DB-->>GraphQL: permissionsUser rows
    GraphQL-->>Client: PermissionsUser[] (with contract field)
Loading

Last reviewed commit: f9f9d89

Copy link
Contributor

Copilot AI left a comment

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 6 out of 6 changed files in this pull request and generated 7 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 11, 2026 18:15 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 11, 2026 18:15 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 11, 2026 18:15 Inactive
Copilot AI review requested due to automatic review settings March 11, 2026 18:29
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 11, 2026 18:29 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 11, 2026 18:29 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 11, 2026 18:29 Inactive
Copy link
Contributor

Copilot AI left a comment

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 8 out of 8 changed files in this pull request and generated 3 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +144 to +149
contract: t.field({
description: "The contract within which these Permissions are granted.",
type: AccountIdRef,
nullable: false,
resolve: ({ chainId, address }) => ({ chainId, address }),
}),
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The new non-null contract field on permissions models is a public schema change (it affects PermissionsResource and PermissionsUser). There are integration tests in this schema package, but none assert contract { chainId address } resolves correctly; adding at least one integration test that queries these new fields would help prevent regressions in ID/contract derivation.

Copilot uses AI. Check for mistakes.
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 11, 2026 18:39 Inactive
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 11, 2026 18:39 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 11, 2026 18:39 Inactive
Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

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

@shrugs Nice work looks good 👍

@lightwalker-eth
Copy link
Member

@greptile

Copy link
Contributor

Copilot AI left a comment

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 14 out of 14 changed files in this pull request and generated 2 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
apps/ensapi/src/graphql-api/schema/domain.ts (1)

331-378: ⚠️ Potential issue | 🟠 Major

Expose the requested registryRoles / resolverRoles API instead of a single permissions connection.

This resolver only scopes permissionsUser through schema.registry, so it can only return registry-backed roles. The PR contract here is registryRoles(where: PermissionsUserWhereInput) and resolverRoles(where: PermissionsUserWhereInput); shipping permissions(where: DomainPermissionsWhereInput) leaves resolver roles unimplemented and changes the public GraphQL schema shape clients will generate against.

Also applies to: 386-391

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/graphql-api/schema/domain.ts` around lines 331 - 378, The
current ENSv2Domain.permissions resolver only returns registry-backed roles and
exposes a single permissions connection (permissions: PermissionsUserRef with
DomainPermissionsWhereInput) which omits resolver-backed roles and changes the
public schema; change the API to expose two separate fields registryRoles(where:
PermissionsUserWhereInput) and resolverRoles(where: PermissionsUserWhereInput),
implement the existing resolver logic as registryRoles (reuse scope/join using
schema.permissionsUser and schema.registry, lazyConnection,
resolveCursorConnection, paginateBy, orderPaginationBy), and add a resolverRoles
implementation that scopes schema.permissionsUser by resource and filters by
resolver-specific join/condition (instead of registry join) so both role sources
are returned and the public GraphQL shape matches the PR contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/schema/registry-permissions-user.ts`:
- Around line 8-10: Replace the standalone JSDoc with a GraphQL description
attached to the schema implementation: remove the top-level JSDoc and add the
text as the description in RegistryPermissionsUserRef.implement({ description:
"..." }) so the type is documented in the GraphQL schema itself and file comment
style remains consistent; ensure the description string matches the original
JSDoc meaning (e.g., "Represents a PermissionsUser whose contract is a Registry,
providing a semantic `registry` field.").

In `@apps/ensapi/src/graphql-api/schema/resolver-permissions-user.ts`:
- Around line 8-10: The JSDoc for the PermissionsUser should be moved into the
GraphQL type description or removed: update
ResolverPermissionsUserRef.implement({ description: ... }) to include the JSDoc
text (or delete the JSDoc comment entirely) so the description appears in schema
introspection and matches file comment consistency; locate the existing JSDoc at
the top of the file and either paste that sentence into the description property
of ResolverPermissionsUserRef.implement or remove the standalone JSDoc block.

In `@apps/ensapi/src/graphql-api/schema/scalars.ts`:
- Around line 140-198: These six identical builder.scalarType definitions
(PermissionsId, PermissionsResourceId, PermissionsUserId, RegistrationId,
RenewalId, ResolverRecordsId) should be factored into a reusable helper (e.g.,
makeIdScalar or createIdScalar) that accepts the scalar name and branded type
and returns the scalarType config; implement the helper near the other scalar
helpers, then replace each builder.scalarType(...) call with a short call like
createIdScalar(builder, "PermissionsId", <BrandedType>) or similar, ensuring the
helper wires description, serialize, and parseValue using
z.coerce.string().transform(...) to cast to the branded type and preserves the
original symbol names (PermissionsId, PermissionsResourceId, PermissionsUserId,
RegistrationId, RenewalId, ResolverRecordsId) so callers and exports remain
unchanged.

---

Duplicate comments:
In `@apps/ensapi/src/graphql-api/schema/domain.ts`:
- Around line 331-378: The current ENSv2Domain.permissions resolver only returns
registry-backed roles and exposes a single permissions connection (permissions:
PermissionsUserRef with DomainPermissionsWhereInput) which omits resolver-backed
roles and changes the public schema; change the API to expose two separate
fields registryRoles(where: PermissionsUserWhereInput) and resolverRoles(where:
PermissionsUserWhereInput), implement the existing resolver logic as
registryRoles (reuse scope/join using schema.permissionsUser and
schema.registry, lazyConnection, resolveCursorConnection, paginateBy,
orderPaginationBy), and add a resolverRoles implementation that scopes
schema.permissionsUser by resource and filters by resolver-specific
join/condition (instead of registry join) so both role sources are returned and
the public GraphQL shape matches the PR contract.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e6d81e79-d806-4334-83a9-f9c458730c26

📥 Commits

Reviewing files that changed from the base of the PR and between 9a51a93 and 4279823.

📒 Files selected for processing (11)
  • apps/ensapi/src/graphql-api/builder.ts
  • apps/ensapi/src/graphql-api/lib/lazy-connection.ts
  • apps/ensapi/src/graphql-api/schema/account.ts
  • apps/ensapi/src/graphql-api/schema/domain.ts
  • apps/ensapi/src/graphql-api/schema/permissions.ts
  • apps/ensapi/src/graphql-api/schema/registration.ts
  • apps/ensapi/src/graphql-api/schema/registry-permissions-user.ts
  • apps/ensapi/src/graphql-api/schema/renewal.ts
  • apps/ensapi/src/graphql-api/schema/resolver-permissions-user.ts
  • apps/ensapi/src/graphql-api/schema/resolver-records.ts
  • apps/ensapi/src/graphql-api/schema/scalars.ts

@shrugs shrugs merged commit e979489 into main Mar 12, 2026
19 of 20 checks passed
@shrugs shrugs deleted the feat/ergonomic-permissions branch March 12, 2026 19:12
@coderabbitai coderabbitai bot mentioned this pull request Mar 13, 2026
2 tasks
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.

ENSv2 GraphQL API: Permissions

3 participants