PulseTrade.Comm.GW 1.0.0-beta2

This is a prerelease version of PulseTrade.Comm.GW.
dotnet add package PulseTrade.Comm.GW --version 1.0.0-beta2
                    
NuGet\Install-Package PulseTrade.Comm.GW -Version 1.0.0-beta2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PulseTrade.Comm.GW" Version="1.0.0-beta2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PulseTrade.Comm.GW" Version="1.0.0-beta2" />
                    
Directory.Packages.props
<PackageReference Include="PulseTrade.Comm.GW" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PulseTrade.Comm.GW --version 1.0.0-beta2
                    
#r "nuget: PulseTrade.Comm.GW, 1.0.0-beta2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PulseTrade.Comm.GW@1.0.0-beta2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PulseTrade.Comm.GW&version=1.0.0-beta2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=PulseTrade.Comm.GW&version=1.0.0-beta2&prerelease
                    
Install as a Cake Tool

PulseTrade.Comm.GW

PulseTrade.Comm.GW is the durable facade package for Generic Gateway hosts. It is intended to be used by a host process that owns HTTP/MCP routes and forwards accepted work into an actor/fabric backend with durable task tickets.

This package deliberately keeps the public durable DTOs Akka-free where they cross the route/task-store boundary. Akka actors, controller refs, and remoting addresses belong to the host/adapter layer, not to persisted task status.

Public Contract

Core DTOs:

  • GatewayDurableRoute: binds a GatewayRoute to delivery mode, task policy, deduplication, and entity-id strategy.
  • GatewayTaskTicket: public ticket returned to callers when work is accepted.
  • GatewayTaskStatus: append-only status row for accepted/running/completed/failed/cancelled work.
  • GatewayTaskResult: completed result cell stored with the task status.
  • GatewayTaskStore: durable store boundary used by JSONL/production stores.
  • GatewayTaskRecoveryDispatchPlan: side-effect-free recovery plan for accepted/running work.

Persisted task status may contain safe command metadata and a GatewayCell<string> command payload. It must not contain IActorRef, raw Akka address objects, bearer tokens, PATs, OAuth secrets, or HTTP/WebSocket response objects.

Minimal Route Sample

let route =
    GatewayRoute.create
        (GatewayRouteKey.webApi "POST" "/ttc" "ttc")
        (ActorRoute "pingpong")
        (GatewaySchema.json "ttc-input" [])
        (GatewaySchema.json "ttc-output" [ "result" ])
        handlerIn
        handlerOut

let durableRoute =
    GatewayDurableRoute.create
        route
        NonShardedDelivery
        GatewayTaskPolicy.defaultLongRunning

let registered =
    runtime.RegisterRoute durableRoute

handlerIn converts public input into a GatewayCell<string> command cell. handlerOut converts a completed result cell into the public response shape.

Route-Level Task Identity

By default, task identity is read from JSON fields such as taskId, operationId, and idempotencyKey. A route can override those field names without replacing the global runtime resolver:

let identityPolicy =
    GatewayTaskIdentityPolicy.create
        [ "jobKey" ]
        [ "phaseKey" ]
        [ "routeIdempotency" ]

let durableRoute =
    GatewayDurableRoute.create route NonShardedDelivery GatewayTaskPolicy.defaultShortRequest
    |> GatewayDurableRoute.withTaskIdentityPolicy identityPolicy

The runtime applies this policy before handlerIn, so repeated requests with the same route-level identity can return the stored task result without recomputing backend work.

Task-Ticket Sample

let! spawned =
    runtime.SpawnTaskAsync route.Key (box inputJson) cancellationToken

match spawned with
| Ok ticket ->
    // Return ticket.TicketId to the caller.
    ()
| Error failure ->
    // Project failure with GatewayDurableProjection.
    ()

A host or recovery worker later dispatches accepted/running tasks:

let! singleTicketPlan =
    runtime.PlanTaskDispatchAsync ticket cancellationToken

let! report =
    runtime.RedeliverOpenTasksAsync cancellationToken

PlanTaskDispatchAsync is the preferred immediate post-spawn seam for durable controller integration: it plans only the requested ticket, keeps the persisted command cell, and can be bound to producer controller handoff without sweeping the entire task store.

Callers query by ticket id:

let! status =
    runtime.QueryTaskByTicketIdAsync ticketId cancellationToken

Controller Result Binding

Host/controller loops should not reimplement task-result persistence, live waiter notification, or delivery confirmation ordering. Use GatewayControllerResultBinding for direct backend completions:

let! completed =
    GatewayControllerResultBinding.complete
        runtime
        ticket
        resultCell
        cancellationToken

When the backend completion comes from a persistent ConsumerController actor, use GatewayControllerResultLifecycle.applyPersistedConsumerCompletion or GatewayControllerResultConfirmBinding.applyPersistedConsumerCompletionAndConfirm.

let! outcome =
    GatewayControllerResultConfirmBinding.applyPersistedConsumerCompletionAndConfirm
        runtime
        ticket
        persistedReply
        confirmTo
        cancellationToken

The required package-level sequence is result before delivery confirm:

  1. Validate the persisted consumer completion against the ticket route, ticket id, and correlation.
  2. Write the terminal task result, failure, or cancellation through CompleteTaskAsync, FailTaskAsync, or CancelTaskAsync.
  3. Notify live waiters only after the terminal task status is persisted.
  4. Send ConsumerController.Confirmed.Instance to the live ConfirmTo actor only after steps 1-3 succeed.

ConfirmTo is a process-local Akka handle. It must never be stored in GatewayTaskStatus, task command metadata, JSONL task stores, task result vaults, or README examples. Recovery redelivers durable command cells and task tickets; it must not replay previous ConsumerController.Confirmed sends or any public HTTP/MCP/WebSocket response side effects.

Actor-owned hosts that let GatewayConsumerActor retain the volatile ConfirmTo handle can set:

let settings =
    GatewayConsumerActorSettings.create routeKey persistenceId clock
    |> GatewayConsumerActorSettings.withControllerConfirmMode ExternalControllerResultBinding

In ExternalControllerResultBinding mode, the consumer actor persists ConsumerProcessingCompleted and returns a confirm-ready plan, but it does not send ConsumerController.Confirmed.Instance by itself. The host must first call GatewayControllerResultLifecycle.applyPersistedConsumerCompletion; after that succeeds, send GatewayConsumerExternalConfirmCommand back to the same consumer actor. The actor then sends ConsumerController.Confirmed.Instance to its retained live ConfirmTo and replies with GatewayConsumerExternalConfirmReply.

Sharded Consumer Controller Start

GatewayConsumerActor can be used as the destination consumer actor under Akka ShardingConsumerController. Configure it with:

let settings =
    GatewayConsumerActorSettings.create routeKey persistenceId clock
    |> GatewayConsumerActorSettings.withShardingConsumerControllerStart shardingConsumerControllerRef

On PreStart, the actor sends ConsumerController.Start<GatewayShardingDeliveryEnvelope> to the configured controller and registers itself as the delivery target. If a host wants the package to create controller props, use GatewayShardingConsumerControllerFactory.props. The factory validates consumer settings and fails closed when the supplied ActorSystem does not expose Akka sharding delivery settings.

Hosts that also want the package to wire the ClusterSharding region can use:

let shardingSettings =
    ClusterShardingSettings.Create(actorSystem).WithStateStoreMode(StateStoreMode.DData)

let consumerRegion =
    GatewayShardingConsumerRegion.startWithSettingsFactory
        actorSystem
        "gateway-sharded-consumer"
        (fun entityId ->
            GatewayConsumerActorSettings.create routeKey ("consumer-" + entityId) clock)
        shardingSettings
        shardCount

This helper starts a real shard region whose entity actor is ShardingConsumerController<GatewayShardingDeliveryEnvelope>, using the same deterministic gateway extractor as GatewayShardingDeliveryRegion. It does not own cluster join, persistence provider selection, durable queue selection, supervision policy, or business completion semantics; those remain host decisions.

Sharded Producer Controller Start

GatewayShardingProducerControllerFactory is the package-side lifecycle seam for hosts that own a shard region. It validates the shard region reference, creates ShardingProducerController<GatewayShardingDeliveryEnvelope> props from a GatewayShardingDeliveryControllerBinding, and can send the ShardingProducerController.Start<GatewayShardingDeliveryEnvelope> message to connect the controller with a GatewayShardingDeliveryProducerActor.

let settings =
    GatewayShardingProducerControllerFactory.create binding shardRegion durableQueueProps

let controllerProps =
    GatewayShardingProducerControllerFactory.props actorSystem settings

This factory still does not own cluster startup, shard-region registration, or service supervision. Hosts must provide those pieces and pass a live shard region actor.

Shard Region Registration Helper

Hosts that own the ClusterSharding region can use the package extractor and registration helper instead of rewriting entity id / payload unwrap rules:

let shardingSettings =
    ClusterShardingSettings.Create(actorSystem).WithStateStoreMode(StateStoreMode.DData)

let shardRegion =
    GatewayShardingDeliveryRegion.startWithProps
        actorSystem
        "gateway-sharded-consumer"
        consumerEntityProps
        shardingSettings
        shardCount

GatewayShardingDeliveryMessageExtractor handles raw GatewayShardingDeliveryEnvelope values, Akka ShardingEnvelope wrappers, and ShardRegion.StartEntity lifecycle messages. It unwraps the Akka envelope to the gateway payload and uses GatewayShardingDeliveryShard.shardId, a deterministic FNV-1a based shard id, instead of .NET randomized string.GetHashCode().

The helper still keeps ownership boundaries strict: the host owns cluster join, shard type naming, sharding settings, region/proxy lifecycle, supervision, journal/snapshot provider, and durable queue choice. The package only provides the stable Gateway envelope extraction/registration seam.

Recovery Rule

Recovery must redeliver command cells to the backend/fabric. It must not replay HTTP responses, WebSocket sends, browser redirects, or MCP JSON-RPC side effects. PlanRecoveryDispatchAsync exists so hosts can inspect dispatchable open tasks without triggering backend work.

Use GatewayStartupRecoveryWorker.executeOnce when the host owns the producer outbox event store and can persist enqueue/attempt events before handoff. Use GatewayStartupRecoveryActorWorker.executeNonShardedOnce when the package GatewayDeliveryProducerActor owns outbox persistence; that helper validates recovery input, sends GatewayDeliveryProducerDispatchCommand to the route producer actor, and treats GatewayDeliveryProducerDispatchAccepted as the startup handoff success boundary. The first actor-owned helper is intentionally non-sharded only.

Producer actors expose explicit outbox snapshot commands for host maintenance loops:

  • GatewayDeliveryProducerSaveSnapshotCommand
  • GatewayShardingDeliveryProducerSaveSnapshotCommand

These commands save a GatewayProducerOutboxSnapshot containing the actor-owned outbox state and safe counts. Snapshot payloads are actor/runtime state, not public request/response side effects.

Store Rule

The first built-in task store is GatewayJsonlTaskStore. The first built-in result payload store is GatewayFileResultVaultStore, exposed through the GatewayResultVaultStore contract and attached with GatewayDurableRuntimeConfig.withResultVaultStore.

CompleteTaskAsync writes the result vault before terminal task status. QueryTaskResultByTicketIdAsync, WaitForTaskResultAsync, and task-centric retry paths can hydrate a completed status whose embedded result payload was compacted away. If neither the status nor the configured vault has the result, the runtime returns result-vault-missing instead of rerunning handlerIn or the backend.

TaskResultReconcileIndexAsync builds a read-only reconcile index from the durable task store latest rows. It lists result conflicts, completed rows whose result payload is missing, failed rows with a retained result, and expired rows that must not be recomputed. Use GatewayDurableProjection.taskResultReconcileIndexToJson or GatewayDurableEndpointAdapter.taskResultReconcileIndex when a host needs public JSON for operator/API diagnostics.

Production hosts may replace both stores with SQL/PCSL-backed stores, but the same DTO rule remains: task status and result vault records are command/result data, not live actor handles.

Solution and Package Boundary

The facade project is expected to stay visible from the root PulseTrade.fs.sln. Consumers should reference PulseTrade.Comm.GW when they need the durable facade APIs; lower packages remain available for hosts that only need the Akka-free contracts or ASP.NET shell.

The NuGet package must carry an explicit PackageId, description, tags, and this README. Release publishing is a separate approved operation; local pack/build validation must not be treated as NuGet push approval.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta2 0 6/22/2026
1.0.0-beta1 30 6/21/2026