@@ -88,6 +88,27 @@ service MetastoreService {
8888 authz : SUPERUSER
8989 };
9090 }
91+
92+ // List cloud topics from the topic table, returning their names, UUIDs,
93+ // and partition counts. Supports pagination by topic name.
94+ rpc ListCloudTopics (ListCloudTopicsRequest )
95+ returns (ListCloudTopicsResponse ) {
96+ option (pbgen.rpc ) = {
97+ authz : SUPERUSER
98+ };
99+ }
100+
101+ // Validate metastore state for a single partition. Checks invariants
102+ // such as extent contiguity, offset bounds, and term ordering.
103+ // Supports pagination via max_extents and resume_after_offset to
104+ // bound the work per call. Cross-page contiguity is enforced by
105+ // checking the boundary between consecutive pages.
106+ rpc ValidatePartition (ValidatePartitionRequest )
107+ returns (ValidatePartitionResponse ) {
108+ option (pbgen.rpc ) = {
109+ authz : SUPERUSER
110+ };
111+ }
91112}
92113
93114// GetOffsetsRequest is the request for looking up the offsets in the metastore
@@ -215,6 +236,7 @@ message MetadataValue {
215236 int64 next_offset = 2 ;
216237 int64 compaction_epoch = 3 ;
217238 uint64 size = 4 ;
239+ uint64 num_extents = 5 ;
218240}
219241
220242// Value for an extent row.
@@ -324,3 +346,96 @@ message ReadRowsResponse {
324346 // Raw key for the next page. Empty if no more rows.
325347 string next_key = 2 ;
326348}
349+
350+ // ValidatePartitionRequest validates metastore invariants for a single
351+ // partition. Supports paginated validation via max_extents and
352+ // resume_at_offset.
353+ message ValidatePartitionRequest {
354+ // The topic UUID to validate.
355+ string topic_id = 1 ;
356+ // The partition ID to validate.
357+ int32 partition_id = 2 ;
358+ // If true, verify each extent's object_id exists in the metastore
359+ // object table and is not a preregistration.
360+ bool check_object_metadata = 3 ;
361+ // If true, verify each extent's L1 object exists in cloud storage
362+ // via HEAD request.
363+ bool check_object_storage = 4 ;
364+ // Resume validation at this base_offset (inclusive). On the next
365+ // page, the validator re-reads the extent at this offset to verify
366+ // cross-page contiguity, then continues forward. The re-read extent
367+ // is not counted toward max_extents. Omit to start from the
368+ // beginning of the partition.
369+ optional int64 resume_at_offset = 5 ;
370+ // Max extents to validate per call (excluding the re-read extent on
371+ // continuation pages). 0 means validate all remaining extents.
372+ uint32 max_extents = 6 ;
373+ }
374+
375+ // ValidatePartitionResponse contains the results of partition validation.
376+ message ValidatePartitionResponse {
377+ // Anomalies found during validation. Empty if the partition is valid.
378+ repeated MetastoreAnomaly anomalies = 1 ;
379+ // The base_offset of the last extent validated. If absent, all
380+ // extents have been validated. Pass this value as resume_at_offset
381+ // in the next call to continue validation.
382+ optional int64 resume_at_offset = 2 ;
383+ // Number of extents validated in this call (excluding the re-read
384+ // extent on continuation pages).
385+ uint32 extents_validated = 3 ;
386+ }
387+
388+ // Type of anomaly found during metastore validation.
389+ enum AnomalyType {
390+ ANOMALY_TYPE_UNSPECIFIED = 0 ;
391+ ANOMALY_TYPE_EXTENT_OVERLAP = 1 ;
392+ ANOMALY_TYPE_EXTENT_GAP = 2 ;
393+ ANOMALY_TYPE_NEXT_OFFSET_MISMATCH = 3 ;
394+ ANOMALY_TYPE_START_OFFSET_MISMATCH = 4 ;
395+ ANOMALY_TYPE_OBJECT_NOT_FOUND = 5 ;
396+ ANOMALY_TYPE_OBJECT_PREREGISTERED = 6 ;
397+ ANOMALY_TYPE_OBJECT_NOT_IN_STORAGE = 7 ;
398+ ANOMALY_TYPE_COMPACTION_RANGE_BELOW_START = 8 ;
399+ ANOMALY_TYPE_COMPACTION_RANGE_ABOVE_NEXT = 9 ;
400+ ANOMALY_TYPE_COMPACTION_TOMBSTONE_OVERLAP = 10 ;
401+ ANOMALY_TYPE_COMPACTION_TOMBSTONE_OUTSIDE_CLEANED = 11 ;
402+ ANOMALY_TYPE_TERM_ORDERING = 12 ;
403+ ANOMALY_TYPE_COMPACTION_STATE_UNEXPECTED = 13 ;
404+ }
405+
406+ // A single anomaly found during metastore validation.
407+ message MetastoreAnomaly {
408+ // The type of anomaly.
409+ AnomalyType anomaly_type = 1 ;
410+ // Human-readable description of the anomaly.
411+ string description = 2 ;
412+ }
413+
414+ // ListCloudTopicsRequest lists cloud topics from the cluster's topic table
415+ // (not the metastore). Hosted here for convenience since the metastore
416+ // admin service already has topic_table access.
417+ message ListCloudTopicsRequest {
418+ // Optional: resume listing after this topic name (exclusive).
419+ // Omit to start from the beginning.
420+ string after_topic_name = 1 ;
421+ // Maximum number of topics to return. 0 uses a default of 100.
422+ uint32 max_topics = 2 ;
423+ }
424+
425+ // ListCloudTopicsResponse contains cloud topics with their metadata.
426+ message ListCloudTopicsResponse {
427+ repeated CloudTopicInfo topics = 1 ;
428+ // If true, more topics are available. Call again with after_topic_name
429+ // set to the last topic's name to continue.
430+ bool has_more = 2 ;
431+ }
432+
433+ // Information about a single cloud topic.
434+ message CloudTopicInfo {
435+ // The Kafka topic name.
436+ string topic_name = 1 ;
437+ // The topic UUID.
438+ string topic_id = 2 ;
439+ // Number of partitions.
440+ int32 partition_count = 3 ;
441+ }
0 commit comments