novadocs
Reference

Reference

gRPC reference

DataPlaneService verb by verb, the append session protocol, status-code mapping, and the admin plane

The gRPC data plane (nova.v1.DataPlaneService, proto/nova/v1/dataplane.proto) is what the Go SDK speaks to any star (any running novad): appends whose sequence numbers the star assigns, with an optional match, reads, subscriptions, stream and bucket CRUD, and the token exchange. Every verb is served by every star; producers, CheckTail, Trim, Fence, and DeleteStream are answered by the stream's leader, with a redirect when you land elsewhere.

Common types

Position is one point on a stream: seq_num (uint64) and timestamp_ms (int64). At an exclusive boundary (end, tail) seq_num is one past the last record and timestamp_ms that record's time, 0 when none is known.

Header: name and value, both opaque bytes. Names are non-empty; duplicates are legal and keep their order; at most 100 per record.

AppendRecord: body (bytes), headers, optional timestamp_ms (the client's stamp, honored per the stream's timestamping mode: absent under client-require refuses the batch, present under arrival refuses it too). A record's size is its content bytes: body plus every header name and value.

Record, as served: seq_num, timestamp_ms, body, headers.

Addressing: every stream-addressed request names its stream by bucket + key (stream_id is a reserved field: the wire retired by-ID naming). A request may create a missing stream on first use where the verb and the bucket's flags allow it (noted per verb).

Stream, the durable record: stream_id, bucket, key, class, producer_policy, retention_max_age_ms, retention_max_bytes (zero = unbounded), batch_max_bytes, batch_max_records, batch_max_delay_ms, throughput_max_bytes_per_second (zero = unlimited), timestamping_mode, timestamping_uncapped, creation_token, created_at_unix_ms, deleted_at_unix_ms (a tombstone).

Bucket: name, location, create_stream_on_append, create_stream_on_read, the defaults as default_class, default_producer_policy, default_retention_max_age_ms, default_retention_max_bytes, default_batch_max_bytes, default_batch_max_records, default_batch_max_delay_ms, default_throughput_max_bytes_per_second, default_timestamping_mode, default_timestamping_uncapped, plus creation_token, created_at_unix_ms, deleted_at_unix_ms.

Enums: Class = CLASS_UNSPECIFIED | CLASS_STANDARD | CLASS_EXPRESS; ProducerPolicy = PRODUCER_POLICY_UNSPECIFIED | PRODUCER_POLICY_ANY | PRODUCER_POLICY_FENCED; TimestampingMode = TIMESTAMPING_MODE_UNSPECIFIED | TIMESTAMPING_MODE_CLIENT_PREFER | TIMESTAMPING_MODE_CLIENT_REQUIRE | TIMESTAMPING_MODE_ARRIVAL. On a create, UNSPECIFIED means "the bucket default's value".

Append verbs

Append

The one-shot append: one call, one durable batch, no session. On Any streams the leader keeps the role warm between calls; a Fenced stream accepts one-shots sequentially. The address form creates the stream when the bucket sets create_stream_on_append.

Request fieldTypeMeaning
bucket, keystringthe stream's address
fencing_tokenstringoptional; presented, never installed
match_seq_numoptional uint64the sequence number the first record must receive; a miss appends nothing
recordsAppendRecord[]the batch

Response AppendResponse: start (first record, inclusive), end (one past the last), tail (the committed tail, at least end).

AppendSession

A bidirectional stream: the producer session. The session is the gRPC stream: it dies with the wire, and a reconnect re-handshakes.

FrameDirectionFields
AppendOpenproducer → star, first frame onlybucket, key; fencing_token
AppendOpenedstar → producer, first responsetail (a hint, instantly stale with concurrent producers), max_inflight_batches (the window, 16 by default)
AppendBatchproducer → star, every later frameoptional match_seq_num, records
AppendAckstar → producer, per batch, in orderstart, end, tail
AppendRejectedstar → producer, in the batch's slottail: a match miss; only this batch was refused, the session lives

Protocol rules:

  • The first frame must be open, or the session ends with InvalidArgument. At the star's session cap it ends with ResourceExhausted.
  • If this star is not the stream's leader, the handshake answers FailedPrecondition with a NotOwner detail; redial there and re-open.
  • Batches pipeline up to the window. When it is full the star stops reading frames: flow control is the backpressure; nothing is buffered or rejected.
  • Acks come back strictly in batch order. A match miss occupies its slot as rejected and the session continues.
  • Any other dispatch failure is terminal for the session: the client reconnects, re-handshakes, and resends its unacked tail. Plain appends are therefore at-least-once across a lost ack; a match batch re-evaluates its match and so is exactly-once.
  • The client half-closes when done; the star flushes the queued acks and ends cleanly, releasing the writer role. The session also ends when its token expires.

Read verbs

Read

One page, answered immediately. The address form creates the stream (empty) when the bucket sets create_stream_on_read.

Request fieldTypeMeaning
start (oneof)seq_num uint64, timestamp_ms int64, tail_offset uint64exactly one: a sequence number, the first record stamped at or after a time, or that many records before the tail
limituint64max records; clamps to the star's cap (8192)
max_bytesuint64payload cap; clamps to the star's cap (8 MiB); at least one record always returns
clampboolpull a start below the trim point or past the tail into range instead of erroring
until_msint64exclusive upper time bound (0: none)

Response ReadResponse: records (empty means caught up) and tail.

Subscribe

A server stream of SubscribeResponse{records, tail} batches from start (oneof seq_num | timestamp_ms, the latter resolved once) onward, pending at the tail as records commit. The address form honors create_stream_on_read. It ends with ResourceExhausted at the star's subscription cap, Unavailable at star shutdown (reconnect elsewhere from your cursor), and at token expiry.

CheckTail

Where the stream ends, strongly consistently: served by the leader when one holds the stream (NotOwner redirect otherwise), from committed state when none does. Request: the stream. Response: tail; seq_num is the next sequence number (0 on a virgin stream). Never creates.

ReadPlan

Where a settled range's bytes live, for consumers that fetch object storage themselves. Request: the stream, start (oneof seq_num | timestamp_ms), limit (entries; clamped), clamp. Response: entries (each a presigned url or a raw object{storage_bucket, object_key}, plus footer_off, footer_len, payload_bytes, first_seq_num, min_timestamp_ms, max_timestamp_ms), optional vended_until (the first sequence not vended; from there up, ask the ordinary verbs), more (truncated at the cap; re-plan from vended_until), and expires_at_unix_ms. Authorized exactly as a read; never names the tail; never creates. The Gateway consumes it internally; the SDK does not expose it.

Lifecycle verbs

CreateStream

bucket and key are required and the bucket must exist. Config fields: class, producer_policy, retention_max_age_ms, retention_max_bytes, batch_max_bytes, batch_max_records, batch_max_delay_ms, throughput_max_bytes_per_second, timestamping_mode, timestamping_uncapped; unset fields take the bucket's defaults. creation_token makes a retry converge on the existing stream. Response: stream.

ReconfigureStream

Applies a partial change to the stream at bucket + key. config is a StreamReconfiguration: the same config fields as a create, every one optional — absent leaves the stream's value unchanged, present (zero included) sets it, so retention_max_age_ms: 0 returns retention to unbounded; a present enum must be named (UNSPECIFIED is InvalidArgument). Holder-served: a led stream answers NotOwner with its holder, which writes the descriptor and, when class, producer policy, timestamping, throughput, or batch changed, hands the stream off before answering — every later append honors the new configuration, acknowledged records keep their class. Retention applies live. A change to a class the star does not serve is FailedPrecondition, refused before anything is written. Authorization: create on the key. Response: stream.

GetStream, DeleteStream, ListStreams

GetStream takes the stream's address and answers stream plus fencing_token: the installed token, read beside the descriptor so a cohort can discover it without provoking a mismatch. DeleteStream takes the stream, is served by the leader (NotOwner redirect), is idempotent, and reclaims in the background; a stream that never existed is NotFound. ListStreams takes bucket (required: listings are per bucket, never across buckets), start_after (the opaque cursor a prior page returned as next), and limit (clamped to 1000); it answers streams and next (empty when complete).

Trim

Advances the trim watermark to point (oneof seq_num | timestamp_ms). Monotonic; records below the point are logically gone at once and reclaimed later. Served by the leader; the address form never creates. Empty response.

Fence

Installs, rotates, or clears (fencing_token empty) the stream's fencing token, as the token's only installer. Served by the leader, which supersedes every live session the new token no longer admits before answering tail, the admission boundary. Rides the append scope; never creates.

Buckets

RPCRequestResponse
CreateBucketconfig (a Bucket; creation_token converges a retry)bucket as stored
GetBucketnamebucket
UpdateBucketconfig, addressed by config.name; flags act at once, defaults govern future streamsbucket
DeleteBucketname; refuses unless emptyempty
ListBucketsstart_after (name cursor), limit (clamped to 1000)buckets, next

ExchangeToken

The one verb an api-key speaks. Request: key_id, secret, exactly one of bucket (grants narrowed to it) or scope (an explicit subset in the nova: grammar), ttl_seconds (0 = the default of 15 min; clamped to 1 h). Response: token, expires_at_unix_ms. Exempt from the auth interceptors. Unimplemented on deployments with no symmetric signing key.

Status codes

The code is for the program, the message for the human, and typed details carry the machine fields.

CodeDetailWhen
FailedPreconditionNotOwner{holder_addr}another star leads the stream: redial holder_addr. An empty holder_addr (a draining star, an unregistered holder) means retry your seeds
FailedPreconditionFence{installed_token}the presented fencing token is not the installed one: match it or fence forward; permanent for the presenter
FailedPreconditionnoneconvergible: a duplicate producer handle, a chain still settling, a departed role owing coverage, a deleted stream's fence, a non-empty bucket on delete; retry
OutOfRangeSequence{expected_next, tail}one-shot match_seq_num miss; nothing appended (sessions answer rejected instead)
OutOfRangenonea read below the trim watermark
InvalidArgumenta record over 1 MiB, a batch over 100 MiB or 65,536 records, over 100 headers or an empty header name, a bad timestamp, a missing start position or address, a malformed exchange ask
NotFoundstream or bucket not found
AlreadyExistsbucket or stream already exists
ResourceExhaustedproducer sessions or subscriptions at the star's cap; the fleet at capacity; back off
Unavailablea transiently terminated epoch; a subscription ended by star shutdown; reconnect
PermissionDeniedno grant covers the action on that address; a revoked credential; an exchange ask wider than the credential
Unauthenticatedno or bad credential; unknown key id or wrong secret on the exchange
Unimplementedthe exchange on a verify-only deployment

The SDK maps these to its sentinels; see the Go SDK.

The admin plane

nova.v1.AdminPlaneService shares the listener. In jwt mode every verb needs nova:admin (SettleStar also accepts nova:node); it targets one star and never redirects.

RPCOne line
Healthstatus (SERVING | DRAINING), node_id, version, location, drained, leading
ListStarsthe live fleet, ascending by star id: id, location, address, load signals
Drainstart (or stop) the star's drain: refuse new leadership, hand off led streams after a grace
Decommissiondurably declare a star gone forever; force bypasses the drained-target guard, cool_down lets its settle schedule run on its own clocks
SettleStarask this star to settle a departing star's unsettled chain epochs (the close-time nudge)
Failpointarm or disarm a named fault hook; test builds only
CreateCredentialregister an api-key with scope; the secret is returned exactly once
UpdateCredentialreplace a credential's scope
RevokeCredentialdisable a credential: exchanges refuse at once, minted tokens live out their TTL
ListCredentialspage key ids, scopes, state, never secrets
GetClusterOverviewevery live star with its stats snapshot, plus the audit pool's snapshots
ListStreamSummariespage stream descriptors with their current leader, optionally scoped to a bucket
ListBucketSummariespage the bucket registry in name order, tombstones included
GetStreamDetailone stream's descriptor, segments (newest first, capped), trim point, pending audit jobs, leader
GetStreamLiveStatsthe leader's in-memory view: tail, tail time, bytes held, hot-read bytes (proxied one hop)
ListAuditQueuesper job type: bounded depth and the oldest ready-at, the reclamation backlog signal
PeekAuditQueuethe first entries of one due queue across its shards, bounded
ReadStreamRecordsone page of records in the read shape, for the admin reader

Source documents

  • proto/nova/v1/dataplane.proto, proto/nova/v1/adminplane.proto, proto/nova/v1/metadata.proto
  • internal/transport/grpc/dataplane/status.go

On this page