Skip to content

fix(projects): send the server's allowlisted Projects query again - #79

Merged
krynju merged 11 commits into
mainfrom
kr/projects-allowlisted-query
Nov 3, 2026
Merged

krynju merged 11 commits into
mainfrom
kr/projects-allowlisted-query

Conversation

@krynju

@krynju krynju commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

jh project list has failed on every JuliaHub instance since v0.4.3:

$ jh project list
Failed to list projects: GraphQL errors: [{query is not allowed}]

Why

JuliaHub answers only allowlisted GraphQL documents. Before #57 the CLI sent JuliaSim's allowlisted Projects document. #57 added paging and a server-side filter, and along the way trimmed fields from projects.gql. The trimmed text matches no allowlisted document, so every instance rejects it. Hasura did the same before graphqlsrvr replaced it.

#57 trimmed the fields for a good reason: JuliaSim's document selects resources, which is expensive. It's still expensive on graphqlsrvr. Timed as admin on nightly.juliahub.dev (61k projects):

allowlisted document 20 rows 100 rows
JuliaSim (juliasim-projects.gql) 24.4 s 504 after 60 s
web UI (frontend/src/components/Projects/queries/projects.gql) 0.5 s 0.5 s

So the CLI now sends the web UI's Projects document for listings. Only two versions of it exist on release branches from 6.9 on, and each of those releases allowlists exactly one. jh clone looks up a single owner/name, so it can afford JuliaSim's document as a last resort: that reaches 6.7 and 6.8, where jh clone worked before this PR. Checked with graphqlsrvr's allowlist Lookup against each release's metadata:

release projects.gql (26.4+ web UI) projects_pre264.gql (6.9–26.3 web UI) projects_juliasim.gql (clone only)
6.7, 6.8 no no yes
6.9, 25.7, 25.8, 25.10, 26.1, 26.2, 26.3 no yes yes
26.4, 26.5, master yes no yes

The CLI sends projects.gql and, when a server rejects it, projects_pre264.gql. "Rejects" means query is not allowed, or a validation-failed "field ... not found in type: 'projects'" from a pre-26.4 server running without an allowlist, whose schema lacks the 26.4 fields. Any other error is returned as it is. If every document is rejected, all the errors are reported. Neither web UI version selects resources, so a listing's fallback is as fast as the first try. Listings never use JuliaSim's document. On 6.7/6.8 jh project list stays unsupported, as it was before this PR. The clone lookup sends JuliaSim's document only after both web UI documents are rejected. It sends it with the owner/name filter and a 10-row limit, which keeps the resources cost to a row or two.

What changed

  • projects.gql and projects_pre264.gql are the two versions of the web UI's Projects document (listDocuments). lookupDocuments adds projects_juliasim.gql, which has the same text as graphqlsrvr/external-queries/juliasim-projects.gql. One fetchProjectsWith tries a list in order. The go:embed comment and CLAUDE.md say the files must stay the server's text, and why listings never use JuliaSim's.
  • jh clone owner/name had its own hand-pasted copy of the query, sent with only ownerId: no limit and no filter. graphqlsrvr caps that at 1000 rows, so on a big install the target could be missing from the page. It now goes through lookupDocuments with a server-side owner/name filter (projectLookupFilter, literal and case-insensitive, sharing ownerUsernameFilter with project list --user). An exact match wins, then the only case-insensitive one. Several case-variant matches without an exact one are reported as ambiguous, and a row that matches neither is not picked. The copy, its separate HTTP client and a second getUserInfo round trip are gone.
  • e2e: skipIfUnsupported and backendGap treated "not allowed" as a backend gap and skipped. That's why TestProjectList and TestProjectListByUser skipped on both nightly installs instead of failing. A disallowed document is a CLI defect, so a single disallowedQuery helper now makes it fail in both. TestCloneNotFound runs the clone lookup against the live server and expects "not found". The README and the package test header say so, and that the suite targets JuliaHub 25.10+, where the allowlist carries the CLI's current Groups and Users documents.
  • Unit tests:
    • TestProjectsDocumentsUnchanged pins all three documents' normalized text and rejects resources in the listing documents. It is a change detector only, because this repo can't see the server's allowlist.
    • The TestFetchProjectsPage* tests cover the fallback: the 26.4+ document first; fall back on query is not allowed or a missing project field only (not on filter validation errors); which documents were sent; and that both errors are kept when neither is accepted.
    • TestLookupProjects* cover the clone lookup's JuliaSim fallback and its 10-row limit. TestPickProject and TestProjectLookupFilter cover choosing the row.

Evidence

  • All three documents checked with JuliaHub's own graphqlsrvr/internal/allowlist Lookup on master and every release from 6.7 (table above). v0.4.3's text matches nothing anywhere. projects_pre264.gql is byte-identical to release-26.3's web UI file.
  • Live on nightly.juliahub.dev:
    project list, 100 rows, admin             200   0.5 s
    clone lookup  owner=TEST name=<existing>  200   rows=1, the right project, 0.25 s
    clone lookup  name=zz_no_such_project     200   rows=0
    v0.4.3 projects.gql                       200   errors: query is not allowed
    
  • go vet ./..., go test ./... and go test -tags e2e -c ./e2e pass.

Not in this PR

  • package_search.gql / package_search_count.gql are allowlisted up to 26.3 but not from 26.4. Package search uses REST first, so only the GraphQL fallback is dead on newer servers. The e2e package and search packages tests skip that case by name (skipIfPackageUnsupported) instead of failing, and the README says so.
  • Server side: JuliaComputing/JuliaHub#25293 adds jh-projects.gql to graphqlsrvr/external-queries/, with the same text as projects.gql. That keeps the 26.4+ document allowlisted when the web UI's file changes, the way jh-users.gql and jh-groups.gql already work.

🤖 Generated with Claude Code

#57 trimmed projects.gql, and JuliaHub answers only allowlisted GraphQL
documents, so since v0.4.3 every `jh project list` gets "query is not
allowed". Restore the allowlisted text (JuliaHub's
graphqlsrvr/external-queries/juliasim-projects.gql); its $limit, $offset,
$orderBy and $filter already carry #57's paging and server-side filter.

The e2e suite skipped on "not allowed", which hid this on both nightly
installs. A disallowed query is a CLI defect, so it now fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@krynju
krynju marked this pull request as ready for review November 2, 2026 11:05
krynju and others added 10 commits November 2, 2026 12:18
Review round 1:
- JuliaSim's allowlisted document selects `resources`, which costs the
  server ~1.2 s per row: an admin's 20-row page took 24 s and a 100-row page
  hit the 60 s gateway timeout on nightly. The web UI's Projects document is
  allowlisted too and answers a 100-row page in 0.5 s, so send that one.
- `jh clone` kept its own hand-pasted copy of the query and sent it with
  only ownerId (no limit, no filter). It now goes through fetchProjectsPage
  with a server-side owner/name filter, and the copy and its HTTP client
  are gone.
- e2e: one disallowedQuery helper; backendGap no longer turns a disallowed
  document into a skip either. Docs updated.
- Unit tests pin projects.gql's normalized text and cover the lookup filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2:
- The web UI's Projects document is allowlisted only from JuliaHub 26.4
  (checked with graphqlsrvr's allowlist Lookup against release-26.3/26.4/
  26.5 and master). Send it first and, when a server answers "query is not
  allowed", JuliaSim's document, which every release allowlists. Any other
  error does not fall back.
- clone: an exact case-insensitive match wins; otherwise a single
  server-side ILIKE match is used (Postgres and Go fold non-ASCII case
  differently). One getUserInfo per clone instead of two.
- ownerUsernameFilter is shared by `project list --user` and clone.
- The hash test is renamed to what it is, a change detector for both
  documents; the fallback order and pickProject have unit tests.
- e2e docs say the package GraphQL fallback isn't allowlisted, so those
  tests fail if REST is down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l back on validation-failed

Review round 3:
- JuliaSim's document costs ~1.2 s per row, so a 100-row page in one
  request runs past the 30 s client timeout. The fallback now fetches the
  same page in ten 10-row requests (about 12 s each for an admin on
  nightly-sized data) and stops at a short chunk.
- A pre-26.4 server running without an allowlist (dev mode) accepts the
  web UI document and then fails on its 26.4-only fields with
  validation-failed; fall back then too.
- e2e: the `jh package` GraphQL fallback is allowlisted up to 26.3 and not
  from 26.4, so a failed REST call there ends in "query is not allowed".
  skipIfPackageFallbackDisallowed skips that known dead fallback by name in
  the package and package-search tests; README corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…im's

Every JuliaHub release from 25.7 allowlists exactly one of two versions of
the web UI's Projects document: the current one (26.4 on) or the one
without `editing_mode` / `requires_publish_request` (25.7–26.3). Checked
with graphqlsrvr's allowlist Lookup against every release branch's
metadata. Neither selects JuliaSim's `resources` (~1 s per row), so the
fallback is as fast as the first try and the 10-row chunking goes away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xact clone matches

- When both Projects documents fail, report both errors, not only the
  fallback's "query is not allowed".
- GraphQL errors print as before ("GraphQL errors: [{message}]"), without
  the extension code.
- pickProject prefers an exact owner/name match over a case-insensitive one.
- TestPackageSearchVerbose skips on the dead package GraphQL fallback like
  the other package tests.
- Fallback tests check which documents were sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, drop the lone-row clone pick

- documentRejected matches "query is not allowed" or a `validation-failed`
  "field ... not found in type", not every validation error, so other errors on
  26.4+ are reported as they are instead of after a doomed fallback.
- pickProject no longer accepts a single row that matches neither exactly nor
  case-insensitively; such a row is not the requested project.
- skipIfPackageUnsupported replaces the copy-pasted skip pair in the package
  and search tests, and runVerbose uses it for `search packages` too.
- e2e/README says the suite targets JuliaHub 25.10+, where jh's Groups and
  Users documents are allowlisted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…project fields

- pickProject returns an error naming the candidates when several projects
  match case-insensitively and none exactly, instead of cloning the newest.
- documentRejected matches only "not found in type: 'projects'", so a filter
  validation error (projects_bool_exp) is reported once, without a fallback.
- runVerbose calls skipIfUnsupported once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`jh clone` on main sent JuliaSim's Projects document, which 6.7 and 6.8
allowlist and neither web UI document is on. The lookup now tries the web UI
documents and then JuliaSim's, with the owner/name filter and a 10-row limit,
so its `resources` cost stays at a row or two. Project listings never use it.

- One fetchProjectsWith walks a document list; listDocuments and
  lookupDocuments define the order. Every rejection is reported.
- "query is not allowed" is matched case-insensitively as a substring.
- TestCloneNotFound runs the clone lookup against the live server.
- runVerboseSkipping takes the skip helper instead of matching argv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…error

- pickProject: an exactly typed project name wins whatever the owner's case
  (names are unique per owner); usernames always compare case-insensitively.
- When several Projects documents fail, each error names its document.
- TestCloneNotFound requires pickProject's own "not found" message, so a
  server validation error ("... not found in type ...") can't pass it.
- graphQLExecutor replaces two copies of the transport closure.
- CLAUDE.md lists the new embedded documents and the request count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- When every document is rejected, the error says so before listing them,
  instead of only stacking "query is not allowed" errors.
- lookupDocuments is a plain literal; fetchProjectsWith keeps only the first
  error and the messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@krynju
krynju requested review from a team and pankgeorg November 3, 2026 09:14

@pankgeorg pankgeorg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we add an integration test as part of the JuliaHub release tests?

@krynju

krynju commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

Can we add an integration test as part of the JuliaHub release tests?

we have jh running as part of nightly, that's how i caught it

@krynju
krynju merged commit dab0d1b into main Nov 3, 2026
1 check passed
@krynju
krynju deleted the kr/projects-allowlisted-query branch November 3, 2026 10:05
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