// Smartbotic Database Service // Document-oriented storage with persistence and replication syntax = "proto3"; package smartbotic.databasepb; // Error code range: 6000-6999 for Database errors // 6000: Generic database error // 6001: Collection not found // 6002: Document not found // 6003: Document already exists // 6004: Version conflict // 6005: Invalid query // 6006: File not found // 6007: File too large // 6008: Encryption error // 6009: Replication error // 6010: Version not found // ===== Main Database Service ===== service DatabaseService { // Document operations rpc Insert(InsertRequest) returns (InsertResponse); rpc Get(GetRequest) returns (GetResponse); rpc Update(UpdateRequest) returns (UpdateResponse); rpc Upsert(UpsertRequest) returns (UpsertResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc PatchDocument(PatchDocumentRequest) returns (PatchDocumentResponse); rpc Exists(ExistsRequest) returns (ExistsResponse); // Version history operations rpc GetVersionHistory(GetVersionHistoryRequest) returns (GetVersionHistoryResponse); rpc GetDocumentVersion(GetDocumentVersionRequest) returns (GetDocumentVersionResponse); rpc RestoreVersion(RestoreVersionRequest) returns (RestoreVersionResponse); rpc RestoreToDate(RestoreToDateRequest) returns (RestoreToDateResponse); // Batch operations rpc BatchInsert(BatchInsertRequest) returns (BatchInsertResponse); rpc BatchGet(BatchGetRequest) returns (BatchGetResponse); rpc BatchDelete(BatchDeleteRequest) returns (BatchDeleteResponse); // Query operations rpc Find(FindRequest) returns (FindResponse); rpc Count(CountRequest) returns (CountResponse); rpc SimilaritySearch(SimilaritySearchRequest) returns (SimilaritySearchResponse); // Set operations (Redis compatibility) rpc SetAdd(SetAddRequest) returns (SetAddResponse); rpc SetRemove(SetRemoveRequest) returns (SetRemoveResponse); rpc SetMembers(SetMembersRequest) returns (SetMembersResponse); rpc SetIsMember(SetIsMemberRequest) returns (SetIsMemberResponse); // Collection management rpc CreateCollection(CreateCollectionRequest) returns (CreateCollectionResponse); rpc DropCollection(DropCollectionRequest) returns (DropCollectionResponse); rpc ListCollections(ListCollectionsRequest) returns (ListCollectionsResponse); rpc GetCollectionInfo(GetCollectionInfoRequest) returns (GetCollectionInfoResponse); // v2.3 — Project management. Projects are top-level namespaces over // collections. The "default" project is implicit and always exists. rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse); rpc CreateProject(CreateProjectRequest) returns (CreateProjectResponse); rpc DropProject(DropProjectRequest) returns (DropProjectResponse); // View management rpc CreateView(CreateViewRequest) returns (CreateViewResponse); rpc DropView(DropViewRequest) returns (DropViewResponse); rpc ListViews(ListViewsRequest) returns (ListViewsResponse); rpc GetViewInfo(GetViewInfoRequest) returns (GetViewInfoResponse); // v2.11.0 T6b — relation (referential integrity) management. Admin-only: // `_relations` is a system collection and a relation names another // collection's schema, so declaring one is not ordinary per-collection // write access. rpc CreateRelation(CreateRelationRequest) returns (CreateRelationResponse); rpc DropRelation(DropRelationRequest) returns (DropRelationResponse); rpc ListRelations(ListRelationsRequest) returns (ListRelationsResponse); rpc GetRelationInfo(GetRelationInfoRequest) returns (GetRelationInfoResponse); // v2.11.0 T5 — "what would happen if I deleted this?" without deleting // it. Read-only and gated as an ordinary per-collection read, not admin. rpc DescribeDelete(DescribeDeleteRequest) returns (DescribeDeleteResponse); // v2.11.0 T7 — "does this relation's reverse index actually match live // data?" A relation declared over a collection that already had rows // before v2.11.0 T7 shipped (or one whose sub-db was lost/restored from // an older snapshot) can carry postings for parent ids that no longer // exist. This walks the reverse index and probes each parent id; // mutates nothing. ⚠ Cost is proportional to the number of DISTINCT // parents referenced, not collection size, but on a very large child // collection that is still a full index walk - treat this as an // operator/migration tool, not something to call on a hot path. // Admin-only, like the other relation-management RPCs above (NOT gated // as a per-collection read on `child`, despite being read-only and // reporting facts about that collection's data): answering requires // resolving the relation first to learn its `child`, and gating after // that lookup would make a nonexistent-relation response distinguishable // from an existing-but-unauthorized one - a probe-able existence oracle, // which is exactly what gate() exists to prevent elsewhere. rpc CheckRelation(CheckRelationRequest) returns (CheckRelationResponse); // Collection configuration rpc ConfigureCollection(ConfigureCollectionRequest) returns (ConfigureCollectionResponse); // v2.9.0 — secondary indexes. Declaration is explicit per collection: an // index costs write throughput, and indexing every field would build useless // ones (a low-cardinality field like a status enum is slower through an // index than a scan). rpc CreateIndex(CreateIndexRequest) returns (CreateIndexResponse); rpc DropIndex(DropIndexRequest) returns (DropIndexResponse); rpc ListIndexes(ListIndexesRequest) returns (ListIndexesResponse); // v2.10.0 — the distinct values an indexed field holds, with row counts. // Answered from the index, one step per distinct value rather than per row. rpc GetIndexValues(GetIndexValuesRequest) returns (GetIndexValuesResponse); rpc GetCollectionConfig(GetCollectionConfigRequest) returns (GetCollectionConfigResponse); rpc MigrateCollectionTimestamps(MigrateCollectionTimestampsRequest) returns (MigrateCollectionTimestampsResponse); // File operations rpc UploadFile(stream FileChunk) returns (UploadFileResponse); rpc DownloadFile(DownloadFileRequest) returns (stream FileChunk); rpc DeleteFile(DeleteFileRequest) returns (DeleteFileResponse); rpc GetFileInfo(GetFileInfoRequest) returns (FileInfo); rpc ListFiles(ListFilesRequest) returns (ListFilesResponse); rpc SetFileTtl(SetFileTtlRequest) returns (SetFileTtlResponse); // Event subscription rpc Subscribe(SubscribeRequest) returns (stream DatabaseEvent); // Health and stats rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse); rpc GetStats(GetStatsRequest) returns (GetStatsResponse); rpc GetMemoryStats(GetMemoryStatsRequest) returns (GetMemoryStatsResponse); // Read-only runtime state control rpc SetReadOnly(SetReadOnlyRequest) returns (SetReadOnlyResponse); rpc GetReadOnlyStatus(GetReadOnlyStatusRequest) returns (GetReadOnlyStatusResponse); } // ===== Replication Service ===== service DatabaseReplication { // Bidirectional streaming for real-time sync rpc SyncStream(stream ReplicationMessage) returns (stream ReplicationMessage); // Pull missed entries (for recovery) rpc GetEntriesSince(GetEntriesRequest) returns (stream ReplicationEntry); // Get current node state rpc GetNodeState(GetNodeStateRequest) returns (NodeState); } // ===== Document Types ===== message Document { string id = 1; string collection = 2; bytes data = 3; // JSON-encoded document data uint64 version = 4; uint64 created_at = 5; // Timestamp in milliseconds uint64 updated_at = 6; uint64 expires_at = 7; // 0 = no expiration string node_id = 8; // Origin node for replication bool encrypted = 9; repeated string encrypted_fields = 10; string created_by = 11; // User ID who created the document string updated_by = 12; // User ID who last updated the document } // ===== Document Operations ===== message InsertRequest { string collection = 1; bytes data = 2; // JSON document string id = 3; // Optional, auto-generated if empty uint32 ttl_seconds = 4; // 0 = use collection default string actor = 5; // User ID performing the operation (for audit) } message InsertResponse { string id = 1; uint64 version = 2; } message GetRequest { string collection = 1; string id = 2; } message GetResponse { Document document = 1; bool found = 2; } message UpdateRequest { string collection = 1; string id = 2; bytes data = 3; uint64 expected_version = 4; // 0 = no version check (force update) string actor = 5; // User ID performing the operation (for audit) // v2.8.0 — change the document's expiry as part of the update. // // `optional` gives three distinct meanings, which a plain uint32 cannot: // absent -> leave the existing expiry alone (the default, and what an // update did implicitly once it stopped WIPING the expiry) // 0 -> clear the expiry: the document becomes permanent // N -> expire N seconds from now // // Before this, ttl_seconds existed only on Insert/Upsert/BatchInsert, so // changing a TTL meant rewriting the whole document via upsert. optional uint32 ttl_seconds = 6; } message UpdateResponse { uint64 new_version = 1; bool success = 2; string error = 3; } message UpsertRequest { string collection = 1; bytes data = 2; string id = 3; uint32 ttl_seconds = 4; string actor = 5; // User ID performing the operation (for audit) } message UpsertResponse { string id = 1; uint64 version = 2; bool inserted = 3; // true if inserted, false if updated } message DeleteRequest { string collection = 1; string id = 2; } message DeleteResponse { bool deleted = 1; } message PatchDocumentRequest { string collection = 1; string id = 2; bytes patch_json = 3; // JSON fields to merge into existing document string actor = 4; // User ID performing the operation (for audit) // v2.8.0 — same three-way semantics as UpdateRequest.ttl_seconds: absent // leaves the expiry alone, 0 clears it, N sets N seconds from now. optional uint32 ttl_seconds = 5; } message PatchDocumentResponse { uint64 new_version = 1; bool success = 2; string error = 3; } message ExistsRequest { string collection = 1; string id = 2; } message ExistsResponse { bool exists = 1; } // ===== Version History Operations ===== message DocumentVersionEntry { uint64 version = 1; bytes data = 2; // JSON-encoded document data at this version uint64 timestamp = 3; // When this version was created (updatedAt) string updated_by = 4; bool encrypted = 5; repeated string encrypted_fields = 6; } message GetVersionHistoryRequest { string collection = 1; string id = 2; uint32 limit = 3; // 0 = all versions uint32 offset = 4; } message GetVersionHistoryResponse { repeated DocumentVersionEntry versions = 1; uint64 current_version = 2; // 0 if document is deleted uint64 total_count = 3; bool document_deleted = 4; } message GetDocumentVersionRequest { string collection = 1; string id = 2; uint64 version = 3; } message GetDocumentVersionResponse { DocumentVersionEntry version_entry = 1; bool found = 2; } message RestoreVersionRequest { string collection = 1; string id = 2; uint64 version = 3; // Version number to restore string actor = 4; // User performing the restore } message RestoreVersionResponse { uint64 new_version = 1; // The newly created version number bool success = 2; string error = 3; } message RestoreToDateRequest { string collection = 1; string id = 2; uint64 timestamp = 3; // Find version active at this time (ms since epoch) string actor = 4; } message RestoreToDateResponse { uint64 restored_version = 1; // Which old version was restored from uint64 new_version = 2; // The newly created version number bool success = 3; string error = 4; } // ===== Batch Operations ===== message BatchInsertRequest { string collection = 1; repeated BatchInsertItem items = 2; string actor = 3; // User ID performing the operation (for audit) } message BatchInsertItem { bytes data = 1; string id = 2; uint32 ttl_seconds = 3; } message BatchInsertResponse { repeated string ids = 1; uint32 success_count = 2; uint32 error_count = 3; } message BatchGetRequest { string collection = 1; repeated string ids = 2; } message BatchGetResponse { repeated Document documents = 1; } message BatchDeleteRequest { string collection = 1; repeated string ids = 2; } message BatchDeleteResponse { uint64 deleted_count = 1; } // ===== Query Operations ===== message FindRequest { string collection = 1; repeated Filter filters = 2; Sort sort = 3; uint32 limit = 4; // Default 100 uint32 offset = 5; repeated string projection = 6; // Fields to include (empty = all) } message Filter { string field = 1; FilterOp op = 2; bytes value = 3; // JSON-encoded value } enum FilterOp { FILTER_OP_UNSPECIFIED = 0; FILTER_OP_EQ = 1; FILTER_OP_NE = 2; FILTER_OP_GT = 3; FILTER_OP_GTE = 4; FILTER_OP_LT = 5; FILTER_OP_LTE = 6; FILTER_OP_IN = 7; FILTER_OP_CONTAINS = 8; FILTER_OP_EXISTS = 9; FILTER_OP_REGEX = 10; FILTER_OP_SEARCH = 11; // Full-text search across ID and string fields } message Sort { string field = 1; bool descending = 2; } message FindResponse { repeated Document documents = 1; uint64 total_count = 2; bool has_more = 3; // WAL fallback metrics (for queries that include evicted documents) bool used_wal_fallback = 4; // True if WAL was scanned for evicted docs uint32 memory_match_count = 5; // Documents matched from memory uint32 wal_match_count = 6; // Documents matched from WAL uint64 memory_search_micros = 7; // Time spent searching memory (µs) uint64 wal_search_micros = 8; // Time spent loading/searching WAL (µs) } message CountRequest { string collection = 1; repeated Filter filters = 2; } message CountResponse { uint64 count = 1; } message SimilaritySearchRequest { string collection = 1; repeated float query_vector = 2; uint32 top_k = 3; float min_score = 4; } message SimilaritySearchResponse { repeated SimilarityResult results = 1; } message SimilarityResult { string id = 1; float score = 2; bytes data = 3; } // ===== Set Operations ===== message SetAddRequest { string collection = 1; string set_id = 2; string member = 3; } message SetAddResponse { bool added = 1; // false if already exists } message SetRemoveRequest { string collection = 1; string set_id = 2; string member = 3; } message SetRemoveResponse { bool removed = 1; } message SetMembersRequest { string collection = 1; string set_id = 2; } message SetMembersResponse { repeated string members = 1; } message SetIsMemberRequest { string collection = 1; string set_id = 2; string member = 3; } message SetIsMemberResponse { bool is_member = 1; } // ===== Collection Management ===== message CreateCollectionRequest { string name = 1; CollectionOptions options = 2; } message CollectionOptions { bool auto_create_id = 1; uint32 default_ttl_seconds = 2; bool encrypted = 3; repeated string sensitive_fields = 4; uint32 max_versions = 5; // Max version history per document (0 = unlimited) uint32 vector_dimension = 6; // Dimension of vectors stored in this collection (0 = not a vector collection) // NEW in v1.7.0 — eviction priority for the collection. // UNSPECIFIED is treated as NORMAL for backward compatibility. MemoryPriority memory_priority = 7; } enum MemoryPriority { MEMORY_PRIORITY_UNSPECIFIED = 0; // treated as NORMAL MEMORY_PRIORITY_LOW = 1; MEMORY_PRIORITY_NORMAL = 2; MEMORY_PRIORITY_HIGH = 3; } message CreateCollectionResponse { bool created = 1; } message DropCollectionRequest { string name = 1; } message DropCollectionResponse { bool dropped = 1; } message ListCollectionsRequest { // v2.8.0 — restrict the listing to one project namespace, mirroring // ListViewsRequest. Empty lists every project (operator/CLI use); clients // always set it, so a workspace never sees another workspace's collection // names. A collection name is information about the shape of someone else's // data even when its contents are unreachable. string project = 1; } message ListCollectionsResponse { repeated string names = 1; } message GetCollectionInfoRequest { string name = 1; } message GetCollectionInfoResponse { CollectionInfo info = 1; bool found = 2; } message CollectionInfo { string name = 1; uint64 document_count = 2; uint64 size_bytes = 3; CollectionOptions options = 4; uint64 created_at = 5; uint64 updated_at = 6; uint32 vector_dimension = 7; // Dimension of vectors stored in this collection (0 = not a vector collection) } // ===== Project Management (v2.3) ===== // // Projects are top-level namespaces over collections. Collections live // inside a project; wire-form "my_app:users" addresses collection "users" // in project "my_app", bare "users" addresses ("default", "users"). The // "default" project is implicit and always present. // // Stage F ships the RPC surface backed by a filesystem-only placeholder // (`/projects//env/`). Stage B replaces the placeholder // with the real ProjectStore-backed implementation. message ListProjectsRequest {} message ListProjectsResponse { // Alphabetical. Always includes "default". repeated string projects = 1; } message CreateProjectRequest { string name = 1; } message CreateProjectResponse { // true = newly created, false = already existed (idempotent) OR validation error. bool created = 1; // Populated on validation failure (e.g. invalid name). Empty on success // and on the idempotent "already existed" path. string error = 2; } message DropProjectRequest { string name = 1; } message DropProjectResponse { bool dropped = 1; // Populated when the drop is refused — e.g. name == "default", // unknown project, or filesystem error. string error = 2; } // ===== File Operations ===== message FileChunk { oneof content { FileMetadata metadata = 1; // First chunk includes metadata bytes data = 2; // Subsequent chunks are data } } message FileMetadata { string id = 1; // Set by server on upload response string name = 2; string mime_type = 3; uint64 size = 4; string file_type = 5; // "plugin", "document", "generated" string related_id = 6; // plugin_id, conversation_id, etc. map metadata = 7; // Custom metadata bool is_public = 8; // Visibility flag // v2.6.0 — owning project. Empty means "default", matching the rule for // bare collection names. Files are namespaced so that per-project access // policy has something to attach to. string project = 9; // v2.8.0 — relative time-to-live in seconds, applied at upload. Converted // once to an absolute expiry server-side, so a file's lifetime does not // restart when it is read or on restart. // // `optional` is load-bearing: ABSENT means "inherit the configured default // for this file type", while an explicitly SET 0 means "never expire" and // overrides that default. A plain uint32 cannot express the difference, // which would make it impossible to opt one file out of a retention policy. optional uint32 ttl_seconds = 10; } message UploadFileResponse { string id = 1; uint64 size = 2; string checksum = 3; // SHA-256 bool deduplicated = 4; // True if blob already existed } message DownloadFileRequest { string id = 1; string project = 2; // v2.6.0 — empty means "default" } message DeleteFileRequest { string id = 1; string project = 2; // v2.6.0 — empty means "default" } message DeleteFileResponse { bool deleted = 1; } message GetFileInfoRequest { string id = 1; string project = 2; // v2.6.0 — empty means "default" } message FileInfo { string id = 1; string name = 2; string mime_type = 3; uint64 size = 4; string file_type = 5; string related_id = 6; string checksum = 7; uint64 created_at = 8; map metadata = 9; bool is_public = 10; // Visibility flag uint32 ref_count = 11; // Blob reference count (informational) string project = 12; // v2.6.0 — owning project uint64 expires_at = 13; // v2.8.0 — absolute expiry ms, 0 = never } message ListFilesRequest { string file_type = 1; // Filter by type (optional) string related_id = 2; // Filter by related ID (optional) uint32 limit = 3; uint32 offset = 4; string checksum = 5; // Filter by checksum (for dedup queries) string name = 6; // Filter by exact filename (optional) string project = 7; // v2.6.0 — empty means "default" } // v2.8.0 — change the expiry of a file that is already stored. A file's TTL was // otherwise fixed at upload. message SetFileTtlRequest { string id = 1; string project = 2; // empty means "default" // Absent is invalid here - the call exists to change the expiry, so it must // say what to. 0 clears the expiry (keep indefinitely); N expires the file N // seconds from now. uint32 ttl_seconds = 3; } message SetFileTtlResponse { bool updated = 1; // false if no such file in that project uint64 expires_at = 2; // the resulting absolute expiry, 0 = never } message ListFilesResponse { repeated FileInfo files = 1; uint64 total_count = 2; bool has_more = 3; } // ===== Event Subscription ===== message SubscribeRequest { repeated string collections = 1; // Empty = all collections repeated string patterns = 2; // Glob patterns (e.g., "assistants:*") bool include_data = 3; // Include document data in events } message DatabaseEvent { EventType type = 1; string collection = 2; string document_id = 3; uint64 timestamp = 4; string node_id = 5; bytes data = 6; // JSON document (if include_data) } enum EventType { EVENT_UNKNOWN = 0; EVENT_INSERT = 1; EVENT_UPDATE = 2; EVENT_DELETE = 3; EVENT_EXPIRE = 4; EVENT_INVALIDATE = 5; // NEW in v1.7.0 — memory-pressure observability events. EVENT_MEMORY_PRESSURE_HIGH = 6; // Emitted when pressure reaches hard threshold EVENT_MEMORY_EVICTION_BURST = 7; // Emitted when eviction runs a large burst } // ===== Replication ===== message ReplicationEntry { uint64 sequence = 1; uint64 global_timestamp = 2; string node_id = 3; OperationType op = 4; string collection = 5; string document_id = 6; uint64 document_version = 7; bytes data = 8; } enum OperationType { OP_UNKNOWN = 0; OP_INSERT = 1; OP_UPDATE = 2; OP_DELETE = 3; OP_UPSERT = 4; OP_CREATE_COLLECTION = 5; OP_DROP_COLLECTION = 6; } message ReplicationMessage { oneof content { ReplicationEntry entry = 1; Heartbeat heartbeat = 2; WatermarkUpdate watermark = 3; SyncRequest sync_request = 4; } } message Heartbeat { string node_id = 1; uint64 timestamp = 2; uint64 sequence = 3; } message WatermarkUpdate { string node_id = 1; uint64 sequence = 2; } message SyncRequest { uint64 from_sequence = 1; } message GetEntriesRequest { uint64 from_sequence = 1; uint32 limit = 2; } message GetNodeStateRequest { } message NodeState { string node_id = 1; uint64 current_sequence = 2; map peer_watermarks = 3; uint64 document_count = 4; repeated string collections = 5; bool healthy = 6; // Per-collection sequence tracking for collection discovery map collection_sequences = 7; } // ===== Health and Stats ===== message HealthCheckRequest { } message HealthCheckResponse { bool healthy = 1; uint64 uptime_seconds = 2; uint64 document_count = 3; uint64 memory_used_bytes = 4; uint64 wal_size_bytes = 5; repeated PeerHealth peers = 6; } message PeerHealth { string node_id = 1; bool connected = 2; uint64 latency_ms = 3; uint64 lag_entries = 4; } message GetStatsRequest { } message GetStatsResponse { uint64 total_documents = 1; uint64 total_collections = 2; uint64 memory_used_bytes = 3; uint64 wal_sequence = 4; uint64 wal_size_bytes = 5; uint64 snapshot_count = 6; uint64 last_snapshot_sequence = 7; uint64 insert_count = 8; uint64 update_count = 9; uint64 delete_count = 10; uint64 query_count = 11; // Memory eviction stats uint64 evicted_documents = 12; // Documents currently evicted to disk uint64 total_evictions = 13; // Total eviction operations performed uint64 recovery_count = 14; // Documents recovered from eviction // Memory configuration uint64 max_memory_bytes = 15; // Configured max memory limit uint32 eviction_threshold_percent = 16; // Start evicting at this % of max uint32 eviction_target_percent = 17; // Evict down to this % of max // Operation timing (microseconds) - for performance monitoring uint64 get_count = 18; // Number of get operations uint64 get_total_micros = 19; // Total time spent in get operations uint64 get_max_micros = 20; // Max single get operation time uint64 insert_total_micros = 21; uint64 insert_max_micros = 22; uint64 update_total_micros = 23; uint64 update_max_micros = 24; uint64 query_total_micros = 25; uint64 query_max_micros = 26; } // ===== View Operations ===== message ViewDefinition { string name = 1; // view name (globally unique, cannot start with _) string collection = 2; // target real collection (must NOT be another view) repeated string include = 3; // field paths to include (dot-notation for nested) repeated string exclude = 4; // field paths to exclude (ignored if include is non-empty) repeated Filter where = 5; // baked-in filters (AND-merged with caller filters) Sort default_sort = 6; // baked-in default sort (used when caller doesn't specify) uint64 created_at = 7; uint64 updated_at = 8; } message CreateViewRequest { string name = 1; string collection = 2; repeated string include = 3; repeated string exclude = 4; repeated Filter where = 5; Sort default_sort = 6; } message CreateViewResponse { bool success = 1; string error = 2; } message DropViewRequest { string name = 1; } message DropViewResponse { bool success = 1; string error = 2; } message ListViewsRequest { // Restrict the listing to one project namespace. Empty lists every // project (operator/CLI use). Clients always set it so a workspace // never sees another workspace's views. string project = 1; } message ListViewsResponse { repeated ViewDefinition views = 1; } message GetViewInfoRequest { string name = 1; } message GetViewInfoResponse { ViewDefinition view = 1; bool found = 2; } // ===== Relation (Referential Integrity) Operations — v2.11.0 T6b ===== // // A relation says: documents in `child` reference documents in `parent` // through `child_field` (a dot-path that may resolve to a single id or an // array of ids). Declarations are persisted in the global `_relations` // system collection (mirrors `_views`) and are project-qualified like any // other collection-carrying name. Management is admin-only. message RelationDefinition { string name = 1; // relation name, project-qualified string child = 2; // collection holding the reference, project-qualified string child_field = 3; // dot-path on the child; may resolve to an array of ids string parent = 4; // collection being referenced, project-qualified // "restrict" (default) | "cascade" | "set_null" | "no_action". // Only restrict/no_action are enforced today (v2.11.0 T4/T6a); cascade // and set_null are accepted and persisted but currently behave as // permit (Task 12 makes them destructive). string on_delete = 5; // Not yet enforced (Task 13). Accepted and persisted. bool validate_on_write = 6; uint64 created_at = 7; uint64 updated_at = 8; } message CreateRelationRequest { string name = 1; string child = 2; string child_field = 3; string parent = 4; string on_delete = 5; // empty defaults to "restrict" bool validate_on_write = 6; } message CreateRelationResponse { bool success = 1; string error = 2; // v2.11.0 T7 — rows indexed by the bootstrap scan. Declaring a relation // over a collection that already has rows backfills the reverse index // immediately (mirrors CreateIndexResponse.rows_indexed), so enforcement // covers pre-existing children from the moment the relation is declared, // not only writes made afterwards. uint64 rows_indexed = 3; } message DropRelationRequest { string name = 1; } message DropRelationResponse { bool success = 1; string error = 2; } message ListRelationsRequest { // Restrict the listing to one project namespace. Empty lists every // project (operator/CLI use). Clients always send their own project so // one workspace never enumerates another's relations. string project = 1; } message ListRelationsResponse { repeated RelationDefinition relations = 1; } message GetRelationInfoRequest { string name = 1; } message GetRelationInfoResponse { RelationDefinition relation = 1; bool found = 2; } // v2.11.0 T5 — "what would happen if I deleted this?", answered without // deleting anything. A relational database exposes its constraints through // DDL you can read; a document store has none, so this has to be a query. // Gated as an ordinary READ on `collection` (it changes nothing), unlike // the relation-management RPCs above which are admin-only. message DescribeDeleteRequest { string collection = 1; string id = 2; } message RelationImpact { string relation = 1; string child_collection = 2; string child_field = 3; string on_delete = 4; // restrict | cascade | set_null | no_action uint64 child_count = 5; repeated string sample_child_ids = 6; // at most five bool blocks = 7; } message DescribeDeleteResponse { bool success = 1; string error = 2; bool would_be_blocked = 3; repeated RelationImpact impacts = 4; } // v2.11.0 T7 — the bootstrap-scan gap. Declaring a relation with T3 alone // only maintains the reverse index going FORWARD from writes made after // declaration; rows already in the child collection were never indexed, so // enforcement silently missed them. This RPC answers "is that still true // right now, for this relation" without changing anything. message CheckRelationRequest { string name = 1; // relation name, project-qualified like the others } // One parent id referenced by the child collection's data, whose parent // document does not exist. message DanglingReference { string parent_id = 1; uint64 child_count = 2; // children of this parent, from the index repeated string sample_child_ids = 3; // at most five } message CheckRelationResponse { bool success = 1; string error = 2; // Every dangling parent id found, not just the reported sample - so a // capped `dangling` list below does not silently understate the problem. uint64 total_dangling = 3; // Capped (see the server implementation) so a badly out-of-sync relation // cannot blow up the response size; total_dangling is the true count. repeated DanglingReference dangling = 4; } // ===== Collection Configuration ===== // Per-collection configuration for timestamp precision and other runtime knobs. // Stored in the _collection_meta system collection. message CollectionConfig { // "ms" (default) — _created_at/_updated_at stamped in milliseconds since epoch // "ns" — _created_at/_updated_at stamped in nanoseconds since epoch // Empty on a ConfigureCollection request means "leave unchanged". string timestamp_precision = 1; // v2.4.5 — version history on/off for an EXISTING collection. // // `optional` (proto3 field presence) is load-bearing, not decoration: a // plain bool defaults to false, so any ConfigureCollection call that meant // to set only timestamp_precision would silently switch versioning OFF. // With presence, an absent field means "leave unchanged" and the server // overlays only what the caller actually set. // // Disabling stops NEW versions being recorded; history already on disk // stays readable, so this is reversible. Distinct from max_versions=0, // which means "unlimited", not "off". optional bool versioning_enabled = 2; // v2.11.0 T8 — whether declared relations are enforced for this collection. // // `optional` for the same reason versioning_enabled is: a plain bool // defaults to false, so a ConfigureCollection call that meant to set // only timestamp_precision would silently switch relation enforcement // OFF. With presence, an absent field means "leave unchanged". // // Defaults to true (enforced) when never explicitly configured - // declaring a relation names its child and parent explicitly, so the // declaration IS the opt-in. optional bool relations_enforced = 3; // Room for future per-collection knobs } message ConfigureCollectionRequest { string collection = 1; CollectionConfig config = 2; } message ConfigureCollectionResponse { bool success = 1; string error = 2; } // v2.9.0 — secondary index management. message CreateIndexRequest { // Project-qualified collection name, as every other collection-carrying // request takes. string collection = 1; // Field to index. Dotted paths address nested fields ("nest.deep"), and the // document metadata fields (_id, _created_at, _updated_at, _version) are // addressable too. string field = 2; // v2.11.0 T11 — when true, the field carries a UNIQUE constraint: two // documents may never hold the same value for it. Declaring this over a // collection that already contains duplicate values is REFUSED (see // CreateIndexResponse.duplicate_examples) rather than accepted and left // silently unenforced against the data that already violates it. bool unique = 3; } message CreateIndexResponse { bool success = 1; string error = 2; // Rows indexed by the backfill. Creating an index over an existing // collection populates it, so it is usable immediately rather than only for // rows written afterwards. uint64 rows_indexed = 3; // True when the index already existed - creation is idempotent. bool already_existed = 4; // v2.11.0 T11 — set when a `unique=true` request was refused because the // field already holds duplicate values. `error` explains the refusal; // these are up to five example document ids that collide, one entry per // colliding value, so the caller can go fix the data rather than guess. repeated string duplicate_examples = 5; } message DropIndexRequest { string collection = 1; string field = 2; } message DropIndexResponse { bool success = 1; string error = 2; } message ListIndexesRequest { string collection = 1; } message IndexInfo { string field = 1; // Distinct values held, and total postings. Both come from the index itself // rather than a scan, so an operator can judge selectivity - a field whose // postings are concentrated in few values will not be used by the planner. uint64 distinct_values = 2; uint64 entries = 3; // v2.11.0 T11 — true when this index also carries a UNIQUE constraint. bool unique = 4; } message ListIndexesResponse { bool success = 1; string error = 2; repeated IndexInfo indexes = 3; } message GetIndexValuesRequest { string collection = 1; string field = 2; // Bounded on purpose: reading the values costs one document read each. uint32 limit = 3; // false walks from the highest value down, so limit=1 gives the maximum. bool ascending = 4; } message IndexValueCount { // The value as stored, JSON-encoded. string value = 1; uint64 count = 2; } message GetIndexValuesResponse { bool success = 1; string error = 2; repeated IndexValueCount values = 3; } message GetCollectionConfigRequest { string collection = 1; } message GetCollectionConfigResponse { CollectionConfig config = 1; bool found = 2; } message MigrateCollectionTimestampsRequest { string collection = 1; string from_precision = 2; // "ms" or "ns" string to_precision = 3; // "ms" or "ns" } message MigrateCollectionTimestampsResponse { bool success = 1; string error = 2; uint64 rows_migrated = 3; // how many documents had their timestamps updated uint64 rows_skipped = 4; // already in target range (idempotent resume) } // ===== Read-Only Control ===== message SetReadOnlyRequest { bool read_only = 1; } message SetReadOnlyResponse { bool success = 1; bool was_read_only = 2; // previous state string error = 3; } message GetReadOnlyStatusRequest {} message GetReadOnlyStatusResponse { bool read_only = 1; string reason = 2; // Recovery outcome details (populated when auto-readonly triggered) string recovery_outcome = 3; // "trivial_success" | "fresh_install" | "snapshot_fell_back" | "wal_only_replay" | "forced_empty" | "failed" string expected_snapshot = 4; string snapshot_used = 5; string failure_reason = 6; uint64 wal_entries_replayed = 7; uint32 snapshots_attempted = 8; } // ===== Memory Stats ===== // Added in v1.7.0. Wire-compatible — server-side implementation lands in T9. message GetMemoryStatsRequest {} message CollectionMemoryStats { string collection = 1; uint64 document_count = 2; uint64 estimated_bytes = 3; uint64 evicted_stub_count = 4; MemoryPriority priority = 5; } message GetMemoryStatsResponse { uint64 total_memory_bytes = 1; uint64 max_memory_bytes = 2; uint32 pressure_percent = 3; // current usage / max × 100 string pressure_level = 4; // "normal" | "soft" | "hard" | "emergency" repeated CollectionMemoryStats collections = 5; // Most recent eviction uint64 last_eviction_timestamp = 6; // ms since epoch, 0 if none uint64 last_eviction_docs = 7; uint64 last_eviction_bytes_freed = 8; }