polyc-state-connect 2026.9.0

State plane transport adapter: capability-specific Connect clients and server-trait glue mapping the generated wire types onto the polyc-state kernel — typed outcomes, per-call admission, and the conformance surface the authenticated shell proves itself against (docs/proposals/separated-planes.md).
//! The State plane's transport adapter.
//!
//! One crate sits between the wire and the kernel, and nothing else in the
//! workspace touches a generated State type (INV-24). A Container composes
//! this crate's server glue; a caller uses its clients; both speak only the
//! vocabulary `polyc_state` defines — commands, receipts, cursors, bounded
//! pages, and typed outcomes.
//!
//! # What lives here
//!
//! - [`wire`] maps the kernel's types onto their encoding and back, one
//!   explicit conversion per type, every field named.
//! - [`error`] carries a typed outcome across the wire as a Connect error
//!   detail, so the variant a module refused with is the variant its caller
//!   reads — never one inferred from a status code.
//! - [`admission`] holds the checks the transport boundary makes before a call
//!   reaches a module: version, audience, the caller's proven identity,
//!   transport budget, and the listener's own lifecycle. Each is a pure
//!   function returning the kernel's typed outcome, so a handler branches on
//!   nothing itself. It also names the plane's audience once
//!   ([`STATE_AUDIENCE`]), for both the caller that declares it and the
//!   listener that serves it.
//! - [`trace`] carries one trace across the hop, so a commit and the decision
//!   that caused it land in the same trace tree.
//! - [`journal`] is the partition journal's client and server glue — the first
//!   authority surface this plane serves.
//! - [`feed`] is the durable commit feed's, including the resumable server
//!   stream a projector tails a partition through.
//! - [`conformance`] serves and dials the conformance kit's synthetic family,
//!   the test surface the authenticated shell proves itself against.
//!
//! # What does not live here
//!
//! Policy. Every decision belongs to `polyc_state` or to an
//! [`admission`] function whose whole body is one comparison; a handler that
//! reached its own conclusion would be a State module hiding inside a
//! transport. Durability, too: a receipt is minted by the module that
//! committed, and this crate only carries it (INV-22).
//!
//! See "Inter-plane protocol and transport",
//! `docs/proposals/separated-planes.md`.

pub mod administration;
pub mod admission;
pub mod burn;
pub mod ceremonies;
pub mod claims;
pub mod conformance;
pub mod credentials;
pub mod error;
pub mod feed;
pub mod immutable;
pub mod ingress;
pub mod journal;
pub mod model_attempt;
pub mod persona;
pub mod persona_memory;
pub mod persona_memory_journal;
pub mod projection;
pub mod query_audit;
pub mod sessions;
pub mod spend;
pub mod tasks;
pub mod trace;
pub mod wire;

/// The largest message this protocol accepts on the wire, in bytes.
///
/// A method-specific body bound is part of the contract, not a deployment
/// knob: a caller knows before it dials what it may send, and a listener
/// refuses anything larger before a handler runs. Deliberately far below the
/// transport library's own multi-megabyte default — State commands are
/// metadata plus a bounded payload, and a listener that would accept a
/// megabyte is a listener that can be made to allocate one.
///
/// Both halves read it: [`conformance::ConformanceClient`] bounds its
/// responses by it, and a listener configures its request bound from it.
///
/// This is the bound every family gets except a family whose semantic command
/// contract explicitly accepts the Versioned State payload ceiling. Those
/// exceptions are the partition journal and credential bootstrap; see
/// [`MAX_JOURNAL_WIRE_MESSAGE_BYTES`] and [`MAX_CREDENTIAL_WIRE_MESSAGE_BYTES`].
pub const MAX_WIRE_MESSAGE_BYTES: usize = 64 * 1024;

/// Largest durable-ingress request accepted on the wire.
///
/// The kernel independently accepts a durable payload and a content-identity
/// representation up to [`polyc_state::ingress::MAX_PAYLOAD_BYTES`] each. The
/// latter is omitted on the wire when it equals the durable payload. The
/// generic allowance covers signed identity, command metadata, and bounded
/// durable-feed drafts.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_INGRESS_WIRE_MESSAGE_BYTES: usize =
    2 * polyc_state::ingress::MAX_PAYLOAD_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

/// Largest work-claim request accepted on the wire.
///
/// A command may carry the kernel's entire bounded change payload. The generic
/// allowance covers identities, metadata, record framing, status, and the
/// attempt history whose count is independently capped at eight.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_CLAIMS_WIRE_MESSAGE_BYTES: usize =
    polyc_state::claims::MAX_CHANGE_PAYLOAD_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

/// Largest query-audit request or reply accepted on the wire.
///
/// A point read can return one intent and one completion, each bounded by the
/// kernel's semantic command limit. An unmatched listing has the tighter
/// [`polyc_state::query_audit::MAX_AUDIT_PAGE_BYTES`] budget and always allows
/// one individually legal intent. The generic allowance covers Buffa framing,
/// call context, page metadata, and cursors.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_QUERY_AUDIT_WIRE_MESSAGE_BYTES: usize =
    2 * polyc_state::query_audit::MAX_AUDIT_PAYLOAD_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

const _: () = {
    assert!(
        polyc_state::query_audit::MAX_AUDIT_PAGE_BYTES + MAX_WIRE_MESSAGE_BYTES
            <= MAX_QUERY_AUDIT_WIRE_MESSAGE_BYTES
    );
};

// The task family keeps a read inside the bound above by filling a list page
// against `polyc_state::tasks::MAX_PAGE_BYTES` rather than by counting rows,
// and it always carries at least one record however large that record is. So
// the largest page it can build is the budget or one whole record, whichever
// is larger, and the reply adds a cursor, two revisions, and Buffa's tags and
// length prefixes on top of it. A quarter of the bound is reserved for that
// framing. Raising either task bound past what this leaves fails to compile
// here instead of failing a list at run time.
const _: () = {
    let budget = polyc_state::tasks::MAX_PAGE_BYTES;
    let record = polyc_state::tasks::MAX_RECORD_BYTES;
    let largest_page = if budget > record { budget } else { record };
    assert!(largest_page + MAX_WIRE_MESSAGE_BYTES / 4 <= MAX_WIRE_MESSAGE_BYTES);
};

/// The largest message the partition-journal family accepts on the wire, in
/// bytes.
///
/// The journal is the one family whose contract already names a payload size,
/// and it names a big one:
/// [`MAX_BATCH_PAYLOAD_BYTES`](polyc_state::journal::MAX_BATCH_PAYLOAD_BYTES)
/// is what the kernel accepts in one atomic batch. A transport bound below it
/// would make that contract false — a caller could be refused a batch the
/// module would have committed — so this family gets a bound that carries the
/// batch the kernel promised, and every other family keeps the tight bound
/// above, which stays a real denial-of-service control rather than a knob.
///
/// Derived, never restated: the kernel's payload and variable-metadata bounds,
/// plus one [`MAX_WIRE_MESSAGE_BYTES`] of headroom for Buffa tags, length
/// prefixes, fixed command fields, and per-record framing neither semantic
/// bound counts. Raising any source bound moves this one with it, so the
/// kernel and transport cannot drift apart through a hand-copied number.
///
/// It bounds both directions, because a read must fit under it too: a journal
/// page is truncated at
/// [`MAX_BATCH_PAYLOAD_BYTES`](polyc_state::journal::MAX_BATCH_PAYLOAD_BYTES)
/// of record payload for exactly that reason.
// `cast_possible_truncation`: the sum is 4 MiB + 64 KiB, which fits a 32-bit
// `usize` with three orders of magnitude to spare. The cast is here rather than
// the constant being written in `usize` because the kernel's bound is the one
// that must be tracked, and it is a `u64`.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_JOURNAL_WIRE_MESSAGE_BYTES: usize = (polyc_state::journal::MAX_BATCH_PAYLOAD_BYTES
    + polyc_state::journal::MAX_BATCH_METADATA_BYTES)
    as usize
    + MAX_WIRE_MESSAGE_BYTES;

/// Largest commit-feed request or stream item accepted on the wire.
///
/// The kernel bounds the chunk's payload plus variable metadata. One additional
/// generic allowance covers Buffa's fixed fields and length framing without
/// permitting the count bound to amplify 4 MiB commits into a 128 MiB message.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_FEED_WIRE_MESSAGE_BYTES: usize =
    polyc_state::feed::MAX_CHUNK_CONTENT_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

/// Largest credential-family request or reply accepted on the wire.
///
/// Bootstrap atomically carries several complete verifier records and the
/// family intentionally derives its resource envelope from Versioned State's
/// payload ceiling. Giving it the generic 64 KiB transport bound would make a
/// valid kernel command unreachable. The extra generic-bound headroom covers
/// Buffa framing and command metadata that the semantic payload count excludes.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_CREDENTIAL_WIRE_MESSAGE_BYTES: usize =
    polyc_state::versioned::MAX_TRANSACTION_PAYLOAD_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

/// Largest persona-memory snapshot request or reply accepted on the wire.
///
/// A snapshot is one versioned entry the kernel bounds at
/// [`MAX_SNAPSHOT_BYTES`](polyc_state::persona_memory::MAX_SNAPSHOT_BYTES), and
/// that bound is asserted against the entry ceiling at compile time. Giving
/// this family the generic 64 KiB transport bound would make a snapshot the
/// kernel says fits unsendable — a refusal a caller could do nothing about,
/// because the record's size is a function of how much a persona remembers.
/// The extra generic-bound headroom covers Buffa framing and the call context
/// the record travels beside.
pub const MAX_PERSONA_MEMORY_WIRE_MESSAGE_BYTES: usize =
    polyc_state::persona_memory::MAX_SNAPSHOT_BYTES + MAX_WIRE_MESSAGE_BYTES;

/// Bound for one authoritative persona-memory journal request or page.
// The kernel bound is 4 MiB and therefore fits every supported `usize`.
#[allow(clippy::cast_possible_truncation)]
pub const MAX_PERSONA_MEMORY_JOURNAL_WIRE_MESSAGE_BYTES: usize =
    polyc_state::persona_memory::journal::MAX_PAYLOAD_BYTES as usize + MAX_WIRE_MESSAGE_BYTES;

/// How many HTTP/2 streams one connection to a State listener may have open at
/// once.
///
/// `SETTINGS_MAX_CONCURRENT_STREAMS` as State's listener advertises it, which is
/// hyper's server default: the listener serves through
/// `connectrpc::axum::serve_tls`, and that path configures a timer on the
/// HTTP/2 builder and nothing else — the setting is reachable only from
/// `connectrpc::Server`, which serves a Connect router rather than the axum one
/// State composes. So this is an observed ceiling, not a chosen one, and it
/// moves only when the listener stops going through that path.
///
/// A caller has to spend it deliberately, because past it nothing pushes back.
/// The h2 client returns `Pending` from its `poll_ready` while a connection is
/// at its stream limit, hyper's HTTP/2 client task then stops draining its
/// dispatch channel, and the unbounded sender feeding that channel keeps
/// accepting: hyper's own `SendRequest::poll_ready` reports whether the
/// connection is closed, never whether it has stream capacity. A request past
/// the ceiling is therefore queued rather than refused — no error, no timeout,
/// and, on a call whose deadline never expires, no end. That is the same
/// failure shape [`feed::MAX_CONCURRENT_SUBSCRIPTIONS`] exists to keep off the
/// listener's blocking pool, one layer down.
///
/// A client that opens long-lived streams therefore bounds its fan-out against
/// this and leaves room for the ordinary calls sharing the connection —
/// `polyc_control_plane::commit_feed`'s `MAX_FOLLOWERS` is derived from it.
pub const MAX_CONCURRENT_STREAMS_PER_CONNECTION: usize = 200;

pub use admission::{
    AudienceBinding, PeerIdentity, STATE_AUDIENCE, check_audience, check_audience_binding,
    check_call_context_version, check_not_draining, check_transport_deadline, state_audience,
};
pub use error::{
    STATE_ERROR_DETAIL_TYPE, TransportFallback, code_for, from_connect_error, to_connect_error,
};
pub use trace::{adopt_caller_trace, bounded_traced_options};
pub use wire::{CallContextVersion, DeclaredCall, declared_call};