Class ProposalStore

java.lang.Object
com.eqixiac.equinix.mcp.server.broker.ProposalStore

public final class ProposalStore extends Object
In-memory, TTL-bounded, single-use store for the Safe Mutation Broker's pending proposals, keyed by confirm token.

Scope: single-process only, like the plan store. Tokens are valid only for the lifetime of this server process; if the MCP host restarts the server, outstanding proposals are gone and must be re-proposed. Entries expire after ttl() (default 10 minutes) and the store keeps at most capacity() live proposals. Minting beyond capacity evicts the oldest live proposal; mint(ChangeType, String, String, String, Instant, String) returns what it evicted so the caller of fabric_propose_change is told, and a later confirm of an evicted token resolves to ProposalStore.Outcome.EVICTED.

Single use, consumed on attempt: consume(String) removes a live proposal on the first attempt, before the caller executes anything - a failed execution does not resurrect the token. Recently consumed and recently expired tokens are remembered (for one further TTL window) so a second confirm gets a precise ProposalStore.Outcome.REPLAYED or ProposalStore.Outcome.EXPIRED answer instead of a generic ProposalStore.Outcome.UNKNOWN.

Thread-safe; all methods synchronize on the store. The clock is injectable for deterministic expiry tests.

  • Field Details

    • DEFAULT_TTL

      public static final Duration DEFAULT_TTL
      Default time-to-live for a pending proposal.
    • DEFAULT_CAPACITY

      public static final int DEFAULT_CAPACITY
      Default maximum number of concurrently pending proposals.
      See Also:
  • Constructor Details

    • ProposalStore

      public ProposalStore()
      Creates a store with the default TTL (10 minutes) and capacity (20 proposals).
    • ProposalStore

      public ProposalStore(Clock clock, Duration ttl, int capacity)
      Creates a store with explicit clock, TTL, and capacity.
      Parameters:
      clock - the time source used for expiry
      ttl - how long a minted proposal stays confirmable
      capacity - the maximum number of concurrently pending proposals
  • Method Details

    • now

      public Instant now()
      Returns:
      the current instant on this store's clock (the reference time a proposal is compiled at)
    • mint

      public ProposalStore.Minted mint(ChangeType changeType, String canonicalSpec, String specSha256, String requestSha256, Instant compiledAt, String priceSummary)
      Mints a proposal: a fresh unguessable token bound to the SHA-256 of the compiled request, expiring one TTL from now. When the store is at capacity the oldest live proposal is evicted first and returned in ProposalStore.Minted.evicted().
      Parameters:
      changeType - the kind of create the proposal performs
      canonicalSpec - the canonical-form spec JSON the dry run validated
      specSha256 - the SHA-256 (hex) of canonicalSpec
      requestSha256 - the SHA-256 (hex) of the compiled request the confirm phase will send
      compiledAt - the reference instant the request was compiled at; the confirm phase recompiles at the same instant
      priceSummary - the one-line price context shown with the proposal; may be null
      Returns:
      the pending change and any proposals evicted to make room for it
    • isExpired

      public boolean isExpired(PendingChange change)
      Whether a proposal's TTL has elapsed on this store's clock. The confirm phase asks again after the human prompt returns, because the prompt may have been open past the expiry.
      Parameters:
      change - a proposal minted by this store
      Returns:
      true when the proposal is no longer within its TTL
    • consume

      public ProposalStore.Consumption consume(String token)
      Consumes a confirm token. A live token is removed on this attempt and returned with ProposalStore.Outcome.CONSUMED; every other outcome carries no change.
      Parameters:
      token - the confirm token presented by fabric_confirm_change
      Returns:
      the consumption result - exactly one of consumed, expired, replayed, or unknown
    • ageOf

      public Duration ageOf(PendingChange change)
      The time elapsed since a proposal was minted, measured on this store's clock.
      Parameters:
      change - a proposal minted by this store
      Returns:
      the age; zero when the clock reads earlier than the mint instant
    • size

      public int size()
      Returns:
      the number of live (confirmable) proposals currently pending
    • ttl

      public Duration ttl()
      Returns:
      this store's time-to-live per proposal
    • capacity

      public int capacity()
      Returns:
      this store's maximum number of concurrently pending proposals