Persisted-operations management surface#50
Merged
Conversation
Adds the admin surface for persisted operations under operations.persistedOperations on the GraphQL schema, plus the infrastructure that backs it: - IPersistedOperationValidator runs HotChocolate schema validation at upsert time so unknown fields, wrong variable types, and bad syntax surface before any row is written. - Structured exception hierarchy (PersistedOperationParseException, PersistedOperationValidationException, ShapeDiffViolationException) with stable codes the GraphQL mutations project into payload errors. - IPersistedOperationsCapability marker registered by UsePersistedOperations so consumers (dashboard) can gate UI on whether the subsystem is wired in. - Management mutations (uploadPersistedOperation, deactivatePersistedOperation, restorePersistedOperation) and queries (persistedOperations, persistedOperation, persistedOperationHistory) under operations.persistedOperations. - Middleware always lets the management surface through enforcement. Decouples version from the id name: ids are opaque, the operation name comes from the document's operation definition, and Version is an optional integer on UpsertOptions. Drops PersistedOperationIdParser and the name_vN parse requirement.
The previous default of 4 was tight enough to reject the new operations.persistedOperations.uploadPersistedOperation payload (operations -> persistedOperations -> uploadPersistedOperation -> errors -> locations -> line is 6 levels), and the earlier "narrow but useful" justification did not survive contact with nested management namespaces or model projections across several FK hops. 15 is generous enough to cover every Trax-shipped surface (management namespaces, model query chains, train discover trees) and any realistic hand-written query, while still rejecting pathological exhaustion probes that nest hundreds of selections deep. Hosts running a public-facing schema can still tighten via MaxExecutionDepth(n). Removes the conditional bump UsePersistedOperations was applying for its own management surface; the new default covers it.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…nches Codecov flagged gaps in the management surface. Closing them with behaviour-specific tests, not coverage padding: - PersistedOperationQueryTests: list ordering by UpdatedAt desc, filter-by-active and filter-by-id-prefix, take/skip pagination, single lookup returning every projected column, missing-id returning null, history newest-first with one row per upsert/deactivate, and empty array for unknown id. - PersistedOperationMutationTests: INVALID_INPUT branches for empty id and empty document on upload, empty reason on deactivate (audit-log integrity), empty id on deactivate and restore, and NOT_FOUND on restoring an id that never existed. - PersistedOperationsMiddlewareTests: two new bypass cases proving any document referencing operations.persistedOperations passes through enforcement (chicken-and-egg with persisting the upload mutation).
|
This PR is included in version 1.28.0 |
Theauxm
added a commit
that referenced
this pull request
May 11, 2026
OperationsQueriesTests, WorkQueueOperationsTests, TraxHealthServiceTests and LogQueriesTests each hand-rolled a connection string with Maximum Pool Size=4;Connection Idle Lifetime=1;Connection Pruning Interval=1 intended to keep the test suite under Postgres's max_connections cap. The cure was worse than the disease: under CI contention, every SetUp paid a full TCP+auth round-trip because the pool prunes every idle connection within 1s. CI Postgres routinely needs >15s for that handshake under load, so the 15s Npgsql default timeout fired and the test failed in SetUp (see #50 CI run 25690772831, WorkQueueOperations SetUp 24s TimeoutException). PR #41 already solved this exact problem class for the AuthE2E tests by dropping the pruning interval and bumping Idle Lifetime + Pool Size + Timeout. Reusing those numbers here: Pool Size=8, Idle Lifetime=30, Timeout=30, Tcp Keepalive=true. Eight connections across four fixtures is 32, still well under postgres default max_connections=100.
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.
Summary
Adds the admin surface for persisted operations end-to-end:
IPersistedOperationValidator, with a HotChocolate-backed implementation registered byUsePersistedOperations. A typo'd field name or wrong variable type now surfaces immediately at upload instead of at request time when a shipped client tries to run the operation.PersistedOperationParseException,PersistedOperationValidationException,ShapeDiffViolationException) with stable codes. The GraphQL mutations catch them and project to payloaderrors[]entries; mutations never throw.IPersistedOperationsCapabilityregistered byUsePersistedOperations. Consumers (the dashboard) probe for it via DI to gate management UI.operations.persistedOperations:uploadPersistedOperation,deactivatePersistedOperation,restorePersistedOperation,persistedOperations(paged list),persistedOperation,persistedOperationHistory. Wired via[ExtendObjectType]so the baseTrax.Api.GraphQLpackage stays free of EF/RabbitMQ deps. The enforcement middleware always lets the management surface through (chicken-and-egg with persisting the upload mutation).name_vNparse rule.OperationNameis taken from the GraphQL document's operation definition;Versionis an optional integer onUpsertOptions.PersistedOperationIdParserdeleted.Test plan
UpsertAsyncvalidator wiring: rejection does not write a row, does not invoke the broadcaster, does not invalidate the cache.IRequestExecutoragainst real Postgres: happy path, parse error projection, schema-mismatch projection, shape-diff with and without bypass, deactivate not-found, restore round-trip.