Skip to content

feat(api): add subnet_endpoints root field#8050

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:gql-subnet-endpoints-7869
Jul 24, 2026
Merged

feat(api): add subnet_endpoints root field#8050
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:gql-subnet-endpoints-7869

Conversation

@tryeverything24

Copy link
Copy Markdown
Contributor

Summary

GET /api/v1/subnets/{netuid}/endpoints and the list_subnet_endpoints MCP tool expose one subnet's endpoint/resource registry with full REST filter/sort/page parity, but GraphQL had no equivalent. The only per-subnet endpoint access was the nested Subnet.endpoints field, which took no arguments and always returned the full list.

This adds a root subnet_endpoints(netuid, ...) field and parameterizes the nested Subnet.endpoints field, both wired to the shared list-query engine — the same pattern the just-merged subnet_candidates (#7878/#8044) and nested Subnet.surfaces (#7885/#8037) fields established.

Closes #7869

What this adds

Root subnet_endpoints(netuid: Int!, ...)JSON

  • Reuses loadSubnetEndpointsList unchanged — the exact loader list_subnet_endpoints calls — so REST, MCP, and GraphQL share one filter/sort/page contract and cannot drift. It reads the baked per-subnet /metagraph/endpoints/{netuid}.json artifact (distinct from the network-wide endpoints(...) registry).
  • Filters: kind, layer, provider, publication_state, status, pool_eligible (a true/false string, matching the reused loader's enum); ranges min_/max_latency_ms and min_/max_score; fields projection; sort + order; limit (1–100) / cursor (Int).
  • Returns the same pagination envelope REST returns: { endpoints, total, returned, limit, cursor, next_cursor, sort, order }.
  • A cold/absent per-subnet artifact stays null (the documented per-subnet contract, mirroring subnet_candidates), never a silently substituted empty list. An invalid netuid or an unsupported filter/sort value surfaces as a BAD_USER_INPUT GraphQL error — not swallowed to [].

Nested Subnet.endpoints(...)[Endpoint!]!

Schema / tooling notes

  • generated/graphql/types.ts regenerated via npm run build:graphql-types; npm run validate:graphql-types-drift passes (types are current with the SDL).
  • The endpoints collection's valid sort fields are kind, last_checked, latency_ms, layer, netuid, pool_eligible, provider, publication_state, score, status (no id) — reflected in the tests.
  • subnet_endpoints is registered in FIELD_COMPLEXITY as a fan-out (relationship) field, matching subnet_candidates.

Tests

Added to tests/graphql.test.ts, covering every added runtime line and branch (100% patch coverage):

  • Root: baked-artifact resolution; null on absent artifact; negative-netuid and unsupported-filter/sort → BAD_USER_INPUT; combined filters; provider / pool_eligible / score-range filtering; sort + page + next_cursor round-trip; unexpected-loader-failure propagation; complexity weight.
  • Nested: filter by kind; filter + sort + limit + cursor paging; full list with no args; unsupported filter/sort → BAD_USER_INPUT; filtering the lazily-loaded (non-bundled) path.

Local gates green: vitest run tests/graphql.test.ts (1015 pass), eslint, prettier --check, tsc --noEmit (no new errors), validate:graphql-types-drift, and the public-safety scan.

GET /api/v1/subnets/{netuid}/endpoints and the list_subnet_endpoints MCP
tool expose one subnet's endpoint/resource registry with full REST
filter/sort/page parity, but GraphQL had no equivalent: the only per-subnet
endpoint access was the nested Subnet.endpoints field, which took no
arguments and always returned the full list.

Add a root subnet_endpoints(netuid, ...) field and parameterize the nested
Subnet.endpoints field, both wired to the shared list-query engine:

- Root subnet_endpoints reuses loadSubnetEndpointsList -- the same loader
  list_subnet_endpoints calls -- so REST, MCP, and GraphQL cannot drift. It
  reads the baked per-subnet /metagraph/endpoints/{netuid}.json artifact
  (distinct from the network-wide endpoints(...) registry), validates netuid
  and every filter/sort value against the REST allowlists, and returns the
  same {endpoints, total, returned, limit, cursor, next_cursor, sort, order}
  envelope. A cold/absent artifact stays null (the per-subnet contract);
  an invalid netuid or filter/sort surfaces as a BAD_USER_INPUT GraphQL
  error, mirroring the subnet_candidates sibling.

- Nested Subnet.endpoints gains the same filter/sort/page arguments, filtered
  through applyQueryFilters against the shared endpoints collection, mirroring
  the nested Subnet.surfaces field (JSONbored#7885). It awaits the parent thunk first
  so the bundled and lazy per-netuid paths both keep working; with no
  arguments the full list passes through unchanged. An unsupported filter/sort
  is a BAD_USER_INPUT error rather than a silently substituted default.
  pool_eligible is a Boolean here and cursor is Int (offset), matching the
  sibling nested field.

Regenerated generated/graphql/types.ts from the SDL.
@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.67%. Comparing base (9d0b380) to head (5a02ac8).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8050   +/-   ##
=======================================
  Coverage   97.67%   97.67%           
=======================================
  Files         220      220           
  Lines       27206    27228   +22     
  Branches    10783    10793   +10     
=======================================
+ Hits        26574    26596   +22     
  Misses        140      140           
  Partials      492      492           
Files with missing lines Coverage Δ
src/graphql-sdl.ts 100.00% <ø> (ø)
src/graphql.ts 99.66% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 24, 2026
@loopover-orb

loopover-orb Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-24 21:01:57 UTC

4 files · 1 AI reviewer · no blockers · readiness 100/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
Adds a root `subnet_endpoints(netuid, ...)` GraphQL field and parameterizes the nested `Subnet.endpoints` field, both reusing the existing `loadSubnetEndpointsList`/`applyQueryFilters` machinery exactly as the prior `subnet_candidates`/`Subnet.surfaces` fields did — error handling (BAD_USER_INPUT vs null-on-cold-artifact vs raw propagation), complexity weighting, and SDL docs all mirror those sibling implementations. Test coverage is thorough: root resolution, cold-artifact null, bad netuid, filter combinations, sort/page/cursor round-trip, unsupported filter/sort errors, unexpected loader-failure propagation, and nested-field filtering against both bundled and lazily-loaded parent lists. The change is internally consistent and matches the established pattern; I found no defect that breaks the code as written.

Nits — 4 non-blocking
  • The nested `Subnet.endpoints(pool_eligible: Boolean)` (src/graphql-sdl.ts) is typed as Boolean while the root `subnet_endpoints(pool_eligible: String)` keeps it a raw `true`/`false` string matching REST/MCP — intentional per-field but worth a one-line doc note since it's an easy trap for API consumers expecting parity.
  • The 14-line inline searchParams-building loop in the nested resolver (src/graphql.ts:~653) duplicates the same pattern already in `subnetEndpointsQueryUrl` (src/subnet-endpoints-mcp.ts) and the sibling `Subnet.surfaces` resolver; acceptable since it follows the existing convention, but a shared helper would remove the triplication as more nested fields get this treatment.
  • The `https:​//graphql.internal/...` placeholder host (src/graphql.ts:653) is hardcoded, consistent with the pre-existing sibling resolver's convention, not a new pattern introduced here.
  • Consider factoring the args→URLSearchParams conversion shared between the nested resolver and `subnetEndpointsQueryUrl` into one exported helper to keep the allowlist/param-name mapping DRY across both call sites.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #7869
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 85 registered-repo PR(s), 38 merged, 3 issue(s).
Contributor context ✅ Confirmed Gittensor contributor tryeverything24; Gittensor profile; 85 PR(s), 3 issue(s).
Linked issue satisfaction

Addressed
The PR adds a root subnet_endpoints(netuid, ...) field with the full filter/sort/page argument set to type Query, parameterizes the nested Subnet.endpoints field with the same arguments, and both reuse the existing loadSubnetEndpointsList loader from subnet-endpoints-mcp.ts rather than reimplementing filtering, exactly as requested.

Review context
  • Author: tryeverything24
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, TypeScript, Python, HTML, C++, Java, PHP, C#
  • Official Gittensor activity: 85 PR(s), 3 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 86ec8e9 into JSONbored:main Jul 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GraphQL parity: add subnet_endpoints(netuid) root field + parameterize the nested Subnet.endpoints field

1 participant