Skip to content

Persisted-operations management surface#50

Merged
Theauxm merged 3 commits into
mainfrom
feature/persisted-ops-admin
May 11, 2026
Merged

Persisted-operations management surface#50
Theauxm merged 3 commits into
mainfrom
feature/persisted-ops-admin

Conversation

@Theauxm
Copy link
Copy Markdown
Member

@Theauxm Theauxm commented May 11, 2026

Summary

Adds the admin surface for persisted operations end-to-end:

  • Schema validation at upsert time via IPersistedOperationValidator, with a HotChocolate-backed implementation registered by UsePersistedOperations. 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.
  • Structured exception hierarchy (PersistedOperationParseException, PersistedOperationValidationException, ShapeDiffViolationException) with stable codes. The GraphQL mutations catch them and project to payload errors[] entries; mutations never throw.
  • Capability marker IPersistedOperationsCapability registered by UsePersistedOperations. Consumers (the dashboard) probe for it via DI to gate management UI.
  • GraphQL surface under operations.persistedOperations: uploadPersistedOperation, deactivatePersistedOperation, restorePersistedOperation, persistedOperations (paged list), persistedOperation, persistedOperationHistory. Wired via [ExtendObjectType] so the base Trax.Api.GraphQL package stays free of EF/RabbitMQ deps. The enforcement middleware always lets the management surface through (chicken-and-egg with persisting the upload mutation).
  • Version decoupled from id name. Ids are now opaque strings — no name_vN parse rule. OperationName is taken from the GraphQL document's operation definition; Version is an optional integer on UpsertOptions. PersistedOperationIdParser deleted.

Test plan

  • Unit tests for the three exception types and the no-op validator (10 tests).
  • Integration tests for the HC schema validator covering parse errors, unknown fields, wrong variable types, multiple errors, cancellation, concurrency.
  • Integration tests for UpsertAsync validator wiring: rejection does not write a row, does not invoke the broadcaster, does not invalidate the cache.
  • Integration tests for the GraphQL mutations end-to-end through IRequestExecutor against real Postgres: happy path, parse error projection, schema-mismatch projection, shape-diff with and without bypass, deactivate not-found, restore round-trip.
  • 215/215 persisted-operation tests pass against the docker-compose Postgres.

Theauxm added 2 commits May 11, 2026 12:43
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.
…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).
@Theauxm Theauxm merged commit a7a75a9 into main May 11, 2026
1 check passed
@Theauxm Theauxm deleted the feature/persisted-ops-admin branch May 11, 2026 18:56
@traxsharp
Copy link
Copy Markdown

traxsharp Bot commented May 11, 2026

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.
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.

1 participant