Skip to content

Replace transient projection query during contract creation #1970

Description

@StuartFerguson

Summary

Contract creation currently performs a transient EventStore projection query to prevent duplicate contract descriptions for an operator. The query polls projection status every 100 ms. This produces repeated gRPC call disposed / Status code: Cancelled messages and creates unnecessary gRPC traffic during contract creation.

Current flow

CreateContract calls ValidateContractCreationPreConditions, which calls RunTransientQuery to scan ContractCreatedEvent events for a matching operator and description.

This relies on transient projections being available and makes contract creation sensitive to projection startup, cancellation, and timing. Production has category/event-type projections disabled.

Required behavior

  • Continue preventing duplicate contract names for the existing uniqueness scope (currently operator + description).
  • Do not use a transient projection or 100 ms polling loop.
  • Do not make the eventually consistent contract read model the correctness authority.
  • Preserve existing random ContractId values and API behavior.
  • Correctly handle concurrent create requests for the same name.

Proposed approach

Introduce a write-side ContractNameAggregate/reservation stream keyed by the normalized uniqueness scope, for example operator ID plus normalized contract name.

The reservation operation should be idempotent:

  • no reservation: create a pending reservation and return the contract ID;
  • same operation retry: return the existing contract ID;
  • confirmed reservation for another contract: return a duplicate-name conflict;
  • pending reservation: return its existing contract ID so creation can be retried safely.

After the contract event is successfully stored, confirm the reservation. If contract creation fails permanently, release it. Add recovery for stale pending reservations.

Migration

Backfill name reservations from existing ContractCreatedEvent events, retaining each contract's current random contract ID. Detect and resolve any existing duplicate operator/name combinations before enforcing the new invariant.

Acceptance criteria

  • Contract creation no longer calls RunTransientQuery or polls projection status.
  • No repeated 100 ms gRPC disposal/cancellation messages are generated by duplicate-name validation.
  • Duplicate names are rejected without relying on read-model eventual consistency.
  • Concurrent requests cannot both create the same operator/name combination.
  • Existing contracts and their random IDs remain valid.
  • Reservation retries are idempotent.
  • Failed or interrupted contract creation cannot leave permanently unusable names without a recovery path.
  • Unit/integration tests cover duplicate, concurrent, retry, failure, and migration/backfill scenarios.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions