appcore-gateway
2.0.0-alpha.2 is the published preview for the sharded tenant state contract
without the removed public tenant and pending maps. Follow the migration guide
before upgrading from stable 1.0.0.
Crate-owned guide and examples
The Runtime repository maintains the detailed guide, basic example, and intermediate example. The wiki summarizes the public boundary; API and executable details live beside the crate code.
Responsibility: tenant-isolated WebSocket relay for Gateway connections between external clients and AppCore workers.
Internal dependencies: contracts, types, security, distributed contracts and peer RPC.
Primary API: GatewayConfig, GatewayState, tenant state, capability
registry and resolver, bounded worker/client connection handles,
MeshPeerTransport, mesh relay request/response DTOs, heartbeat pruner and
Axum router factory. Opaque content-envelope transport contracts are reexported
for encrypted payload routing.
Current RC migration: direct access to
GatewayState::tenantshas been removed so unrelated tenants no longer share one lock. Code using that field intentionally fails to compile; usetenant_partition,tenant_partition_or_insert,tenant_countandconnection_count. Pending-response maps are private; usepending_request_countfor observation and letEnvelopeRouterown their generation-bound lifecycle. No compatibility alias or mirror map is provided.
The private directory stores 32 immutable copy-on-write shard generations.
Admission, heartbeat and HA scans clone only those 32 Arc handles and release
all shard locks before inspecting shared tenant partitions; no complete tenant
list is allocated or cloned.
The gateway resolves a tenant from the deployment-owned domain suffix or an explicit local-test query parameter, authenticates connections when configured, routes Peer RPC envelopes and mesh-relayed Peer RPC HTTP requests only inside the tenant partition, and tracks stale worker connections with bounded outbound queues.
The normal Runtime activation path is the existing deployment adapter map:
[adapters.gateway]
provider_id = "appcore-gateway"
settings = { bind_address = "127.0.0.1:8080", domain_suffix = "gateway.example.com", heartbeat_interval_ms = "30000", heartbeat_timeout_ms = "90000" }
secret_refs = {}
Cluster mode additionally requires absolute paths.gateway_replay to name one file on
a writable volume shared by every Gateway instance.
The parser accepts only those four non-secret settings. Endpoints, secret
references, unknown settings and authentication overrides fail closed.
The deployment executable adds and authorizes the owner descriptor runtime.gateway in the
shared capability catalog, reuses Runtime security and registers the instance
as a critical Supervisor-managed service. Without adapters.gateway, it
creates no Gateway runtime, listener or task.
Authenticated upgrades accept credentials only in the Authorization header;
query credentials are rejected. Worker tokens use worker_connection_hash to
bind tenant, cluster, installation, Core and capabilities. Client tokens use
client_connection_hash to bind tenant, cluster and device. Both are one-use
peer tokens with a unique jti, a request hash and at most 60 seconds of
lifetime; the socket expires with the token.
The mesh relay validates its V1 schema and the inner Peer RPC routing metadata, body digest and signed request hash before forwarding. Application payloads remain opaque. Frames/messages are limited to 4 MiB; tenant, connection, capability, pending-request, timeout, queue and concurrent-routing limits fail closed. Heartbeats require the exact JSON heartbeat shape, and a worker response is accepted only from the selected connection generation.
mesh-relay is a peer transport for Cores that keep outbound-only Gateway
connections instead of exposing local ports or stable IPs. It is not a
consensus system, public TLS terminator or production secret manager. Edge
relay federation and alternative transports must not weaken Peer RPC
authentication, expiry, nonce or replay protections.
The Runtime host uses a durable, process-safe FilePeerNonceStore: standalone
places it in private Runtime storage, while cluster fails closed unless
absolute paths.gateway_replay selects a shared writable file. Active sockets expire in
at most 60 seconds. Direct embedders may inject another PeerNonceStore; their
default is bounded and process-local. Source-IP rate limiting and TLS
termination remain deployment controls.
GatewayRuntime owns its listener, current-thread Tokio runtime, router,
heartbeat pruner and runtime thread. Startup binds synchronously, so an invalid
or occupied address aborts host startup. Bounded cooperative shutdown joins all
owned work. Before the deadline it force-drops the server future, closing slow
or incomplete connections before joining the thread. Orphaned is only a
defensive thread-failure quarantine. Safe snapshots contain lifecycle state,
bind addresses and counters only. Direct users of
spawn_heartbeat_pruner must retain and await the returned join handle.
HA federation transfers each admitted request to its bounded blocking worker,
then moves the encoded JSON buffer directly into HttpRequest. The complete
inner Peer RPC payload is not cloned at either ownership boundary.
The outer credential uses json_payload_hash to stream canonical JSON into
SHA-256, so hashing retains no second complete encoded body.
Worker and client connection hashes use canonical V2 binary framing and carry
a v2: marker. Earlier unversioned hashes are not interchangeable; token
issuers and Gateway consumers must be upgraded together.
Hashing now borrows all validation fields. At the maximum 64 x 128-byte worker
capability shape, it writes directly into the required 17 KiB hexadecimal
output without retaining the former 8.5 KiB binary frame or a second 17 KiB
hash-only string. Five calibrated Apple M1 release samples reduced worker-hash
p50 by 7.77%, workload RSS delta by 22.73% and retained delta by 19.05%; the
client-hash p50 fell 19.54%.
Each tenant keeps bounded direct worker indexes by Core ID and by
(cluster_id, core_id). The common unique-Core lookup is O(1); duplicate Core
IDs use a scan bounded by the tenant worker ceiling. Register, reconnect,
disconnect and heartbeat pruning update the primary map, capability registry
and indexes under the same tenant lock. Saturating rebuild and inconsistency
counters expose index health without unbounded labels.
In the current Runtime beta, the reverse capability registry shares one
immutable name owner across all worker advertisements in a tenant while
preserving the direct routing index. capabilities_for_iter provides a stable
borrowed view and stats reports only distinct names, workers, advertisements
and unique UTF-8 name bytes. Removing the final advertiser releases the shared
name. At the 1,024-worker/64-capability limits on Apple M1, peak RSS fell from
30.20 to 27.70 MiB and p50 lookup from 55.53 to 50.22 ns.
1.0.2-rc: Redis HA registry
This is development status, not functionality in the stable 1.0.0 package.
The private Runtime beta through
deff156
defines GatewayRegistryProvider, implements RedisGatewayRegistryProvider
and adds the bounded GatewayHaCoordinator. It uses monotonic tenant-local instance epochs,
exact instance/worker-generation fences, one Redis Cluster hash slot per
tenant, bounded timeouts/concurrency and separately resolved zeroizing
credentials. Plain Redis endpoints are loopback-only; remote endpoints require
rediss://. An ambiguous mutation is never retried automatically.
The provider caps each tenant at 1,024 workers, 4,096 sessions and 2,048 pending requests. A real Redis 7.4 conformance run passed two-provider ownership, tenant isolation, schema rejection, stale/duplicate completion, all three capacity walls, connection kill, explicit reconnect and recovery with a higher epoch. Windows GNU cross-compilation passed; the macOS-hosted Linux cross-check lacked a Linux OpenSSL sysroot and is not Linux evidence.
The lifecycle gate in
756b794
rejects HTTP/WebSocket admission, dispatch and response completion outside
Healthy. The coordinator acquires every configured tenant epoch before
Healthy, renews or rolls back the exact set in serialized rounds, and limits
each round to 64 concurrent operations and five seconds. Unconfigured
single-instance state keeps its existing behavior.
The Runtime now owns that task and replays every bounded live worker and
unexpired session before Healthy. New sockets reach shared ownership before
local admission; disconnect, heartbeat pruning and shutdown remove exact
records. The local route now claims origin/target epochs and worker generation before
dispatch, completes before returning success, and cancels on queue failure,
timeout or shutdown. Owner-aborted futures leave only a 30-second TTL-bounded
record. Fixed counters expose claims/completions/cancellations without request
labels.
The authenticated V2 federation endpoint now binds the exact request body,
source/target epochs and worker generation to a separate short-lived one-use
credential. The target validates the shared claim before touching the socket,
returns typed AC-021 errors, and the origin completes the fence before accepting
the response. The combined E2E uses two Gateway states, independent Redis 7.4
connections and Caddy 2.11.4 as the only advertised route to the target. It
drops the owner ungracefully, waits for the bounded lease TTL, reacquires a
higher epoch and routes through Caddy again in under five seconds. The clean
AC-022 local report at 7197416 passed
all subsystems: shared lookup stayed at 0.58--0.67 us p99, 1,000-tenant
recovery at 2.25 ms p99, local fenced routing at 0.35 ms p99 and federated
routing at 0.91 ms p99. Linux and Windows CI evidence remains required before
the HA profile is deployable, and it must never fall back locally. Track
public AC-013.
1.0.3-rc: bounded worker selection
FirstAvailable remains the default and now chooses in stable worker-identity
order instead of process-random HashSet order. The exhaustive V1
SelectionPolicy remains limited to FirstAvailable; the new non-exhaustive
WorkerSelectionPolicy carries opt-in RoundRobin, LeastInflight,
HealthWeighted and Affinity policies. Consumers of the earlier RC draft
only replace the enum name; manifests and wire contracts do not change.
CapabilityResolver::select considers only the current tenant's advertised
workers and returns typed failures for an absent capability, no healthy worker,
all workers at capacity, or invalid affinity.
Health is bounded by heartbeat age; least-inflight also uses outbound queue depth as a deterministic tie-breaker. Affinity accepts at most 128 bytes and uses stateless tenant-local rendezvous hashing, so it retains no growing key map. Selection happens before the caller signs a Peer RPC envelope. Gateway dispatch never rewrites its V1 target and independently acquires a permit for at most 64 concurrent routes per worker, released on every terminal path.
The final clean macOS/aarch64 gate at
7caddc1
measured 17,125 ns round-robin p99, 18,542 ns least-inflight p99 and 38,083 ns
affinity p99 across 64 workers. Exact round-robin distribution, health,
capacity and stateless-affinity invariants passed. This is repository-local
evidence, not production or cross-platform certification.
Selection now borrows worker identities. First-available, least-inflight and affinity scan without a candidate list; round-robin and health-weighted retain one compact borrowed buffer to preserve stable ordered distribution. At the 1,024-worker ceiling, five calibrated Apple M1 release samples reduced round-robin p50 from 468.86 to 335.56 us (-28.43%) and p95 from 471.98 to 339.67 us (-28.03%). Only the selected owned result clones its key.
Candidate lookup now uses the existing Core index without constructing owned installation/Core tuple keys. An exact scan bounded by 1,024 workers preserves identity when installations share a Core ID. In matched complete Gateway certification runs, allocation operations fell from 7,439,239 to 810,640 (-89.10%), requested bytes fell 45.21%, and selection p99 improved by 15.63% to 20.87%.
1.0.4-rc: bounded routing telemetry
The current RC exposes a vendor-neutral pull snapshot through
GatewayMetrics::telemetry_snapshot and an explicit
GatewayTelemetryExporter boundary. It records fixed route outcomes,
inflight/peak routes, queue saturation, reconnects, retries, authentication and
export failures, plus fixed-bucket route latency, worker wait, lock wait and
payload observations. Capability cardinality is capped at 128 series; further
validated names are combined in the fixed
appcore.gateway.capability.overflow series. Tenant, connection, request,
token and payload values never become labels.
Runtime owners use GatewayRuntime::details for additive telemetry and HA
state. The constructible V1 GatewayRuntimeSnapshot keeps its original field
set, and GatewayMetrics retains its stable unwind-safety contract.
Routing never invokes an exporter. Deployment-owned Prometheus or
OpenTelemetry adapters pull the owned snapshot and control their own queue,
retry and transport policy. A clean release-profile certification at
implementation commit 31c4fbe
measured 1,792 ns p99 for an instrumented unavailable-worker route and 5,792 ns
p99 for a 129-series snapshot, against budgets of 1 ms and 5 ms respectively.
These measurements are repository-local evidence, not production traffic or
collector certification.
Maturity: stable peer transport profile for the V1 distributed surface.