PulseTrade.Comm.GW
1.0.0-beta2
dotnet add package PulseTrade.Comm.GW --version 1.0.0-beta2
NuGet\Install-Package PulseTrade.Comm.GW -Version 1.0.0-beta2
<PackageReference Include="PulseTrade.Comm.GW" Version="1.0.0-beta2" />
<PackageVersion Include="PulseTrade.Comm.GW" Version="1.0.0-beta2" />
<PackageReference Include="PulseTrade.Comm.GW" />
paket add PulseTrade.Comm.GW --version 1.0.0-beta2
#r "nuget: PulseTrade.Comm.GW, 1.0.0-beta2"
#:package PulseTrade.Comm.GW@1.0.0-beta2
#addin nuget:?package=PulseTrade.Comm.GW&version=1.0.0-beta2&prerelease
#tool nuget:?package=PulseTrade.Comm.GW&version=1.0.0-beta2&prerelease
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 aGatewayRouteto 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:
- Validate the persisted consumer completion against the ticket route, ticket id, and correlation.
- Write the terminal task result, failure, or cancellation through
CompleteTaskAsync,FailTaskAsync, orCancelTaskAsync. - Notify live waiters only after the terminal task status is persisted.
- Send
ConsumerController.Confirmed.Instanceto the liveConfirmToactor 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:
GatewayDeliveryProducerSaveSnapshotCommandGatewayShardingDeliveryProducerSaveSnapshotCommand
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 | Versions 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. |
-
net10.0
- Akka (>= 1.5.69)
- Akka.Cluster (>= 1.5.69)
- Akka.Cluster.Sharding (>= 1.5.69)
- Akka.Persistence (>= 1.5.69)
- FAkka.FCell2 (>= 10.1.300)
- FSharp.Core (>= 10.1.301)
- PulseTrade.Comm.GW.Abstractions (>= 1.0.0-beta2)
- PulseTrade.Comm.GW.AspNetCore (>= 1.0.0-beta2)
- PulseTrade.Comm.GW.Core (>= 1.0.0-beta2)
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 |