THOTH-GQL-DATALOADER-01: add request-scoped GraphQL DataLoader foundation - #802
Merged
ja573 merged 29 commits intoAug 12, 2026
Merged
Conversation
Context now owns RequestLoaders directly (loaders field) with the single four-argument Context::new constructor; with_guard_mode, the batch_store field, the temporary GraphqlBatchStore alias module and the obsolete test_context_with_guard_mode helper are removed, together with the RequestLoaders::new legacy-bridge constructor. The API server handler constructs the context guard-independently; its preserved guard HTTP test documentation is restored from base.
Two inherited tests failed for real defects in the fixtures, not in the foundation. batch_wide_in_memory_failure asserted an impossible response shape: children is a non-null list, so a per-key error null-propagates the whole data object; the test now proves batch totality at the loader level and correct propagation at the GraphQL level. event_contains_shape_metadata used a baseline-invalid request: pinned Juniper rejects an absent non-null variable even when the definition has a default, so the guard correctly emitted no event; the variable is now supplied. Also strengthens assertions that previously indexed into JSON null and passed vacuously, and renames fixture access to the migrated Context.loaders field.
Replaces juniper::execute_sync in the central execute_graphql helper and the six direct call sites with async juniper::execute driven through one bounded block_on_graphql bridge. The bridge builds a current-thread Tokio runtime per call and fails explicitly with an actionable message when invoked from inside a running runtime; both behaviours are covered by dedicated tests. Async execution is now the supported general GraphQL test execution shape; no synchronous compatibility requirement remains for future loader-backed fields.
cargo-generated resolution of the approved dataloader 0.18.x line: exactly dataloader 0.18.0 with its single tokio dependency edge, matching the default-features-off, runtime-tokio configuration in thoth-api/Cargo.toml.
The inherited scheduling fixture slept 1 ms on half the cohort and asserted that dispatch fragmentation must occur; on a loaded CI runner the whole cohort still coalesced into one dispatch and the test failed. ADR-0007 only claims a delayed cohort MAY fragment, so the fixture now demonstrates the property deterministically: the delayed resolvers perform unrelated awaited work until the loader's first dispatch has demonstrably happened, then register their keys, which can only land in a later batch. This removes the wall-clock dependence (and the now-unneeded dev-only tokio time feature) while still characterizing why loader-first is a binding adoption rule.
ja573
marked this pull request as ready for review
August 12, 2026 10:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
THOTH-GQL-DATALOADER-01 — request-scoped GraphQL DataLoader foundation
STATUS: READY FOR INDEPENDENT EXACT-HEAD REVIEW
Task identity
THOTH-GQL-DATALOADER-01developat060052b47490d3d977db3b9d9f188c4c70760a9afeature/shared-architecture/graphql-dataloader-foundationa1d73aaf0d65b01df2d8cbcd07daad60c2be269ddocs/engineering/ai-delivery/tasks/THOTH-GQL-DATALOADER-01.md(APPROVED)ADR-0007(APPROVED; supersedesADR-0006)docs/engineering/ai-delivery/implementation-reports/THOTH-GQL-DATALOADER-01-implementation-report.mdThis PR is the one bounded implementation authorized by the CTO. It adopts
no production GraphQL field and carries no merge, deployment,
release, production-access or activation authorization. It remains DRAFT
pending independent exact-head review and separate explicit CTO merge
authorization.
What this PR delivers
Foundation (ADR-0007 B0)
dataloader 0.18.0(default features off,runtime-tokioon) and directtokio(rt) inthoth-api;Cargo.lockcommitted.RequestLoadersowned directlyby the real GraphQL
Context(Context.loaders), created and dropped perrequest; no global/static/app-data loader; availability independent of
mutation guard mode.
200, yield count10(
configured_loader), never crate defaults.try_load-only load API for database loaders; total, fail-closed batchfunctions; batch-wide failure yields an error for every requested key,
never fabricated empty success, never per-key fallback/retry SQL.
tokio::task::spawn_blocking:Arc<PgPool>andowned keys enter the closure; connection acquired, used and dropped
entirely inside it; set-based
eq_anySQL; no connection across.await.SharedBatchErrorprojection preserving each fieldfamily's actual current GraphQL error convention (generic
Displayorexplicit
extensions.type); grep-level test proves no serde round trip.Async test execution (spec §3.8)
juniper::execute_syncto async Juniper through one bounded central bridge (
block_on_graphql);all 7 former
execute_syncsites migrated; nested-runtime misuse failsexplicitly with a clear message, with tests for both behaviours.
A2 retirement (spec §7) and guard decoupling (spec §8)
batching.rs(store),prefetch.rs,scope.rs,batching_fixture.rs,batching_tests.rs, theContext.batch_storefield,
Context::with_guard_mode,test_context_with_guard_mode,MutationGuardMode::store_available(), and all ADR-0006guard-as-store-switch documentation. The temporary handoff-time
GraphqlBatchStorealias is gone.semantics,
run_mutation_guardat the request boundary, the CLI/env moderesolution, and default/production mode
OFF. No activation, norequest-acceptance change.
regressions, baseline-invalid matrix,
@skip/@includedirectivecross-checks against Juniper's actual observed execution counts, and the
duplicate top-level mutation execution evidence (still executes once per
occurrence under async execution — this defect is NOT fixed by this PR
and remains separately controlled under ADR-0007 §4.13/§7.4).
Acceptance evidence (spec §10)
[1], 100→[100], 200→[200],201→
[200, 1], 500→[200, 200, 100].deliberately delayed cohorts fragment (documented as expected scheduler
behaviour — why loader-first is a binding adoption rule);
loader-behind-loader keeps the target at 1 dispatch.
(batching, not caching), and read-write-read freshness without
invalidation.
try_load(no panic, no fabricatedsuccess); batch-wide failure is an error for every key from exactly one
dispatch.
[200, 50]→exactly 2 set-based (
= ANY) imprint statements captured by Dieselconnection
Instrumentation(external observation, not an internalcounter).
conventions (full serialized GraphQL response equality; no retry/fallback
dispatch).
Validation gate (local, disposable Postgres 17 + Redis; exact results in the implementation report)
Operational effects
Authorization boundaries — unchanged
NOT authorized by this PR: merge, deployment, release, production access,
workflow dispatch,
BE-02, Thoth Metrics adoption,OBSERVE,ENFORCE,PR #799 remediation/merge/closure, or architecture changes outside ADR-0007.
PR #799 remains outside this task and untouched.
Next gate
Independent exact-head review of
a1d73aaf0d65b01df2d8cbcd07daad60c2be269d, then separateexplicit CTO merge authorization. The implementing agent does not approve or
merge its own work.