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 field | Type | Meaning |
|---|---|---|
bucket, key | string | the stream's address |
fencing_token | string | optional; presented, never installed |
match_seq_num | optional uint64 | the sequence number the first record must receive; a miss appends nothing |
records | AppendRecord[] | 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.
| Frame | Direction | Fields |
|---|---|---|
AppendOpen | producer → star, first frame only | bucket, key; fencing_token |
AppendOpened | star → producer, first response | tail (a hint, instantly stale with concurrent producers), max_inflight_batches (the window, 16 by default) |
AppendBatch | producer → star, every later frame | optional match_seq_num, records |
AppendAck | star → producer, per batch, in order | start, end, tail |
AppendRejected | star → producer, in the batch's slot | tail: a match miss; only this batch was refused, the session lives |
Protocol rules:
- The first frame must be
open, or the session ends withInvalidArgument. At the star's session cap it ends withResourceExhausted. - If this star is not the stream's leader, the handshake answers
FailedPreconditionwith aNotOwnerdetail; 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
rejectedand 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 field | Type | Meaning |
|---|---|---|
start (oneof) | seq_num uint64, timestamp_ms int64, tail_offset uint64 | exactly one: a sequence number, the first record stamped at or after a time, or that many records before the tail |
limit | uint64 | max records; clamps to the star's cap (8192) |
max_bytes | uint64 | payload cap; clamps to the star's cap (8 MiB); at least one record always returns |
clamp | bool | pull a start below the trim point or past the tail into range instead of erroring |
until_ms | int64 | exclusive 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
| RPC | Request | Response |
|---|---|---|
CreateBucket | config (a Bucket; creation_token converges a retry) | bucket as stored |
GetBucket | name | bucket |
UpdateBucket | config, addressed by config.name; flags act at once, defaults govern future streams | bucket |
DeleteBucket | name; refuses unless empty | empty |
ListBuckets | start_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.
| Code | Detail | When |
|---|---|---|
FailedPrecondition | NotOwner{holder_addr} | another star leads the stream: redial holder_addr. An empty holder_addr (a draining star, an unregistered holder) means retry your seeds |
FailedPrecondition | Fence{installed_token} | the presented fencing token is not the installed one: match it or fence forward; permanent for the presenter |
FailedPrecondition | none | convergible: 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 |
OutOfRange | Sequence{expected_next, tail} | one-shot match_seq_num miss; nothing appended (sessions answer rejected instead) |
OutOfRange | none | a read below the trim watermark |
InvalidArgument | — | a 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 |
NotFound | — | stream or bucket not found |
AlreadyExists | — | bucket or stream already exists |
ResourceExhausted | — | producer sessions or subscriptions at the star's cap; the fleet at capacity; back off |
Unavailable | — | a transiently terminated epoch; a subscription ended by star shutdown; reconnect |
PermissionDenied | — | no grant covers the action on that address; a revoked credential; an exchange ask wider than the credential |
Unauthenticated | — | no or bad credential; unknown key id or wrong secret on the exchange |
Unimplemented | — | the 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.
| RPC | One line |
|---|---|
Health | status (SERVING | DRAINING), node_id, version, location, drained, leading |
ListStars | the live fleet, ascending by star id: id, location, address, load signals |
Drain | start (or stop) the star's drain: refuse new leadership, hand off led streams after a grace |
Decommission | durably declare a star gone forever; force bypasses the drained-target guard, cool_down lets its settle schedule run on its own clocks |
SettleStar | ask this star to settle a departing star's unsettled chain epochs (the close-time nudge) |
Failpoint | arm or disarm a named fault hook; test builds only |
CreateCredential | register an api-key with scope; the secret is returned exactly once |
UpdateCredential | replace a credential's scope |
RevokeCredential | disable a credential: exchanges refuse at once, minted tokens live out their TTL |
ListCredentials | page key ids, scopes, state, never secrets |
GetClusterOverview | every live star with its stats snapshot, plus the audit pool's snapshots |
ListStreamSummaries | page stream descriptors with their current leader, optionally scoped to a bucket |
ListBucketSummaries | page the bucket registry in name order, tombstones included |
GetStreamDetail | one stream's descriptor, segments (newest first, capped), trim point, pending audit jobs, leader |
GetStreamLiveStats | the leader's in-memory view: tail, tail time, bytes held, hot-read bytes (proxied one hop) |
ListAuditQueues | per job type: bounded depth and the oldest ready-at, the reclamation backlog signal |
PeekAuditQueue | the first entries of one due queue across its shards, bounded |
ReadStreamRecords | one 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.protointernal/transport/grpc/dataplane/status.go