syntax = "proto3";

package filer_pb;

option go_package = "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb";
option java_package = "seaweedfs.client";
option java_outer_classname = "FilerProto";

//////////////////////////////////////////////////

service SeaweedFiler {

    rpc LookupDirectoryEntry (LookupDirectoryEntryRequest) returns (LookupDirectoryEntryResponse) {
    }

    rpc LookupDirectoryEntries (LookupDirectoryEntriesRequest) returns (LookupDirectoryEntriesResponse) {
    }

    rpc ListEntries (ListEntriesRequest) returns (stream ListEntriesResponse) {
    }

    rpc CreateEntry (CreateEntryRequest) returns (CreateEntryResponse) {
    }

    rpc UpdateEntry (UpdateEntryRequest) returns (UpdateEntryResponse) {
    }

    rpc TouchAccessTime (TouchAccessTimeRequest) returns (TouchAccessTimeResponse) {
    }

    rpc AppendToEntry (AppendToEntryRequest) returns (AppendToEntryResponse) {
    }

    rpc DeleteEntry (DeleteEntryRequest) returns (DeleteEntryResponse) {
    }

    rpc ObjectTransaction (ObjectTransactionRequest) returns (ObjectTransactionResponse) {
    }

    rpc ObjectTransactionBatch (ObjectTransactionBatchRequest) returns (ObjectTransactionBatchResponse) {
    }

    rpc PosixLock (PosixLockRequest) returns (PosixLockResponse) {
    }

    rpc AtomicRenameEntry (AtomicRenameEntryRequest) returns (AtomicRenameEntryResponse) {
    }
    rpc StreamRenameEntry (StreamRenameEntryRequest) returns (stream StreamRenameEntryResponse) {
    }

    rpc StreamMutateEntry (stream StreamMutateEntryRequest) returns (stream StreamMutateEntryResponse) {
    }

    rpc AssignVolume (AssignVolumeRequest) returns (AssignVolumeResponse) {
    }

    rpc LookupVolume (LookupVolumeRequest) returns (LookupVolumeResponse) {
    }

    rpc CollectionList (CollectionListRequest) returns (CollectionListResponse) {
    }

    rpc DeleteCollection (DeleteCollectionRequest) returns (DeleteCollectionResponse) {
    }

    rpc Statistics (StatisticsRequest) returns (StatisticsResponse) {
    }

    rpc Ping (PingRequest) returns (PingResponse) {
    }

    rpc GetFilerConfiguration (GetFilerConfigurationRequest) returns (GetFilerConfigurationResponse) {
    }

    rpc TraverseBfsMetadata (TraverseBfsMetadataRequest) returns (stream TraverseBfsMetadataResponse) {
    }

    rpc SubscribeMetadata (SubscribeMetadataRequest) returns (stream SubscribeMetadataResponse) {
    }

    rpc SubscribeLocalMetadata (SubscribeMetadataRequest) returns (stream SubscribeMetadataResponse) {
    }

    // List the metadata subscribers currently connected to this filer
    // (FUSE mounts, S3, filer.sync, peer filers, ...).
    rpc ListMetadataSubscribers (ListMetadataSubscribersRequest) returns (ListMetadataSubscribersResponse) {
    }

    rpc KvGet (KvGetRequest) returns (KvGetResponse) {
    }

    rpc KvPut (KvPutRequest) returns (KvPutResponse) {
    }

    rpc CacheRemoteObjectToLocalCluster (CacheRemoteObjectToLocalClusterRequest) returns (CacheRemoteObjectToLocalClusterResponse) {
    }

    rpc DistributedLock(LockRequest) returns (LockResponse) {
    }
    rpc DistributedUnlock(UnlockRequest) returns (UnlockResponse) {
    }
    rpc FindLockOwner(FindLockOwnerRequest) returns (FindLockOwnerResponse) {
    }
    // distributed lock management internal use only
    rpc TransferLocks(TransferLocksRequest) returns (TransferLocksResponse) {
    }
    rpc ReplicateLock(ReplicateLockRequest) returns (ReplicateLockResponse) {
    }

    // Peer chunk sharing — tier 1: mount-server registry.
    // See design-weed-mount-peer-chunk-sharing.md for details.
    rpc MountRegister (MountRegisterRequest) returns (MountRegisterResponse) {
    }
    rpc MountList (MountListRequest) returns (MountListResponse) {
    }
}

//////////////////////////////////////////////////

message LookupDirectoryEntryRequest {
    string directory = 1;
    string name = 2;
}

message LookupDirectoryEntryResponse {
    Entry entry = 1;
    // filer log position stamped before the entry read: every event at or
    // below it is reflected in the returned entry
    int64 log_ts_ns = 2;
    // signature of the filer whose clock stamped log_ts_ns; positions are
    // only comparable with events that filer logged
    int32 log_signature = 3;
}

message ListEntriesRequest {
    string directory = 1;
    string prefix = 2;
    string startFromFileName = 3;
    bool inclusiveStartFrom = 4;
    uint32 limit = 5;
    int64 snapshot_ts_ns = 6;
    // Leave the chunk list out of every entry in the response. The attributes
    // still carry the file size, so a listing that only reads attributes can
    // ask for this and skip the largest part of the payload.
    bool omit_chunks = 7;
}

message ListEntriesResponse {
    Entry entry = 1;
    int64 snapshot_ts_ns = 2;
}

message RemoteEntry {
    string storage_name = 1;
    int64 last_local_sync_ts_ns = 2;
    string remote_e_tag = 3;
    int64 remote_mtime = 4;
    int64 remote_size = 5;
    // unset when the remote listing does not report encodings (S3);
    // empty when the remote object authoritatively has none
    optional string remote_content_encoding = 6;
}
message Entry {
    string name = 1;
    bool is_directory = 2;
    repeated FileChunk chunks = 3;
    FuseAttributes attributes = 4;
    map<string, bytes> extended = 5;
    bytes hard_link_id = 7;
    int32 hard_link_counter = 8; // only exists in hard link meta data
    bytes content = 9; // if not empty, the file content

    RemoteEntry remote_entry = 10;
    int64 quota = 11; // for bucket only. Positive/Negative means enabled/disabled.
    int64 worm_enforced_at_ts_ns = 12;
}

message FullEntry {
    string dir = 1;
    Entry entry = 2;
}

message EventNotification {
    Entry old_entry = 1;
    Entry new_entry = 2;
    bool delete_chunks = 3;
    string new_parent_path = 4;
    bool is_from_other_cluster = 5;
    repeated int32 signatures = 6;
}

enum SSEType {
    NONE = 0;    // No server-side encryption
    SSE_C = 1;   // Server-Side Encryption with Customer-Provided Keys  
    SSE_KMS = 2; // Server-Side Encryption with KMS-Managed Keys
    SSE_S3 = 3;  // Server-Side Encryption with S3-Managed Keys
}

message FileChunk {
    string file_id = 1; // to be deprecated
    int64 offset = 2;
    uint64 size = 3;
    int64 modified_ts_ns = 4;
    string e_tag = 5;
    string source_file_id = 6; // to be deprecated
    FileId fid = 7;
    FileId source_fid = 8;
    bytes cipher_key = 9;
    bool is_compressed = 10;
    bool is_chunk_manifest = 11; // content is a list of FileChunks
    SSEType sse_type = 12;           // Server-side encryption type
    bytes sse_metadata = 13;         // Serialized SSE metadata for this chunk (SSE-C, SSE-KMS, or SSE-S3)
}

message FileChunkManifest {
    repeated FileChunk chunks = 1;
}

message FileId {
    uint32 volume_id = 1;
    uint64 file_key = 2;
    fixed32 cookie = 3;
}

message FuseAttributes {
    uint64 file_size = 1;
    int64 mtime = 2; // unix time in seconds
    uint32 file_mode = 3;
    uint32 uid = 4;
    uint32 gid = 5;
    int64 crtime = 6; // unix time in seconds
    string mime = 7;
    int32 ttl_sec = 10;
    string user_name = 11; // for hdfs
    repeated string group_name = 12; // for hdfs
    string symlink_target = 13;
    bytes md5 = 14;
    uint32 rdev = 16;
    uint64 inode = 17;
    int64 ctime = 18; // unix time in seconds, inode change time
    int32 mtime_ns = 19; // nanosecond component of mtime (0-999999999)
    int32 ctime_ns = 20; // nanosecond component of ctime (0-999999999)
    int32 crtime_ns = 21; // nanosecond component of crtime (0-999999999)
    int64 atime = 22; // unix time in seconds, last access time
    int32 atime_ns = 23; // nanosecond component of atime (0-999999999)
}

message CreateEntryRequest {
    string directory = 1;
    Entry entry = 2;
    bool o_excl = 3;
    bool is_from_other_cluster = 4;
    repeated int32 signatures = 5;
    bool skip_check_parent_directory = 6;
    // Optional precondition evaluated against the current entry atomically with
    // the write, under the filer's per-path lock.
    WriteCondition condition = 7;
    // Set on a create a filer forwarded to the entry's ring owner, so the owner
    // applies it locally instead of forwarding again. Clients leave it unset.
    bool is_moved = 8;
}

// WriteCondition is the precondition the filer evaluates against the existing
// entry before writing, under the per-path lock. A failed condition returns
// FilerError PRECONDITION_FAILED. The client maps request semantics (e.g. RFC
// 7232) to clauses; the filer just compares.
//
// A condition is a list of clauses that ALL must hold (logical AND). One clause
// is the common case; several express what a single comparison cannot: an ETag
// set (If-Match / If-None-Match with multiple values), weak-ETag comparison, and
// compound conditions (e.g. If-Match + If-Unmodified-Since together).
message WriteCondition {
    enum Kind {
        NONE                  = 0; // unconditional
        IF_NOT_EXISTS         = 1; // fail if the entry exists (If-None-Match: *)
        IF_EXISTS             = 2; // fail if the entry is absent (If-Match: *)
        IF_ETAG_MATCH         = 3; // fail if absent or etag matches none of the set (If-Match)
        IF_ETAG_NOT_MATCH     = 4; // fail if present and etag matches any of the set (If-None-Match)
        IF_UNMODIFIED_SINCE   = 5; // fail if present and mtime > unix_time
        IF_MODIFIED_SINCE     = 6; // fail if present and mtime <= unix_time
        IF_EXTENDED_NOT_EQUAL = 7; // fail if present and extended[ext_key] == ext_value
        IF_EXTENDED_TIME_ELAPSED = 8; // fail if present and extended[ext_key] (unix seconds) is in the future
        IF_CHUNKS_EQUAL       = 9; // fail unless the stored chunk fid multiset equals fids (absent entry = no chunks)
    }
    // Clause is one primitive comparison. IF_ETAG_MATCH holds when the current
    // entry's ETag equals any value in etags; IF_ETAG_NOT_MATCH holds when it
    // equals none. allow_weak permits weak-comparison (ignoring the W/ prefix).
    //
    // The IF_EXTENDED_* kinds are generic guards on an extended attribute, used
    // to enforce object-lock without teaching the filer S3 semantics:
    // IF_EXTENDED_NOT_EQUAL expresses a legal hold (block while a key equals a
    // value), and IF_EXTENDED_TIME_ELAPSED expresses retention (block while a
    // stored unix-second deadline is in the future, compared to the filer's
    // clock). The caller composes these and, for governance-bypass, simply omits
    // the retention clause when the bypass is authorized — the filer makes no
    // authorization decision.
    //
    // IF_CHUNKS_EQUAL guards a chunk-preserving read-modify-write: the stored
    // chunk fid set must still equal what the caller read, so a stale write
    // cannot resurrect needles that a concurrent update already diffed away
    // and queued for deletion. An empty fids list expects no chunks.
    message Clause {
        Kind kind = 1;
        repeated string etags = 2; // ETag set for IF_ETAG_* kinds
        int64 unix_time = 3;       // bound (unix seconds) for IF_*_SINCE kinds
        bool allow_weak = 4;       // compare ETags ignoring the weak (W/) marker
        string ext_key = 5;        // extended attribute name for IF_EXTENDED_* kinds
        string ext_value = 6;      // blocking value for IF_EXTENDED_NOT_EQUAL
        string gate_key = 7;       // IF_EXTENDED_TIME_ELAPSED: only enforce when extended[gate_key] == gate_value
        string gate_value = 8;     // gate value (e.g. retention mode COMPLIANCE for governance bypass)
        repeated string fids = 9;  // chunk fid strings for IF_CHUNKS_EQUAL
    }
    repeated Clause clauses = 1;   // all must hold (logical AND)
}

// Structured error codes for filer entry operations.
// Values are stable — do not reorder or reuse numbers.
enum FilerError {
    OK                    = 0;
    ENTRY_NAME_TOO_LONG   = 1; // name exceeds max_file_name_length
    PARENT_IS_FILE        = 2; // parent path component is a file, not a directory
    EXISTING_IS_DIRECTORY = 3; // cannot overwrite directory with file
    EXISTING_IS_FILE      = 4; // cannot overwrite file with directory
    ENTRY_ALREADY_EXISTS  = 5; // O_EXCL and entry already exists
    PRECONDITION_FAILED   = 6; // WriteCondition not satisfied
}

// ObjectMutation is one entry-level change applied by ObjectTransaction. All
// mutations of a transaction run under a single per-path lock (the request's
// lock_key) and in order, so the gateway can describe a multi-entry object
// operation as one request instead of holding a distributed lock across
// several RPCs. Data-bearing writes (entries with chunks) should be written
// before the transaction; mutations here are metadata-scoped.
message ObjectMutation {
    enum Type {
        PUT              = 0; // create or replace the entry (entry field)
        DELETE           = 1; // delete the entry at directory/name (no error if absent)
        PATCH_EXTENDED   = 2; // merge set_extended / remove delete_extended on the entry
        RECOMPUTE_LATEST = 3; // scan a directory and re-point a parent entry (recompute)
    }
    Type type = 1;
    string directory = 2;
    string name = 3;                       // entry name for DELETE / PATCH_EXTENDED / RECOMPUTE_LATEST (the pointer entry)
    Entry entry = 4;                        // full entry for PUT
    map<string, bytes> set_extended = 5;   // PATCH_EXTENDED: keys to set
    repeated string delete_extended = 6;   // PATCH_EXTENDED: keys to remove
    bool is_delete_data = 7;               // DELETE: also delete chunk data
    bool is_recursive = 8;                 // DELETE: recurse into a directory
    Recompute recompute = 9;               // RECOMPUTE_LATEST parameters
    bool set_content = 10;                 // PATCH_EXTENDED: replace Entry.content with content
    bytes content = 11;                    // PATCH_EXTENDED: new Entry.content when set_content
    bool touch_mtime = 12;                 // PATCH_EXTENDED: set the entry's Mtime to now (e.g. a metadata-replace copy)
    bool remove_empty_parent = 13;         // DELETE: also remove the parent directory when the delete leaves it empty (best-effort)
}

// Recompute re-derives a pointer entry (directory/name on the mutation) from the
// current contents of a scanned directory, atomically under the transaction's
// lock. It is mechanical: the filer picks the child that sorts first or last by
// name and copies the requested fields into the pointer; it has no knowledge of
// what the entries mean. The caller (which does know the versioning scheme)
// supplies the sort direction and the key mappings. This covers re-pointing the
// latest version after a specific version is deleted, where the scan must run
// under the lock.
message Recompute {
    string scan_dir = 1;                   // directory whose direct children are scanned
    bool descending = 2;                   // pick the child that sorts last by name (else first)
    map<string, string> copy_extended = 3; // pointer extended key -> source extended key on the chosen child
    string name_to_key = 4;                // if set, store the chosen child's name under this pointer key
    string size_to_key = 5;                // if set, store the chosen child's FileSize (decimal) under this pointer key
    string mtime_to_key = 6;               // if set, store the chosen child's Mtime (decimal) under this pointer key
    string demote_key = 7;                 // if set, stamp demote_value on the prior name_to_key target when it changes
    bytes demote_value = 8;                // value for demote_key
    string exclude_name = 9;               // if set, skip this child when scanning (e.g. a version about to be deleted)
}

// ObjectTransactionRequest applies an ordered list of mutations atomically with
// respect to other writers of the same object, by holding the filer's per-path
// lock on lock_key for the whole transaction. The optional condition is checked
// first, against condition_key when set, else lock_key. Callers set route_key to
// the object's stable owner ring key; a filer that is not the owner forwards the
// transaction one hop to the owner, so a stale ring view is tolerated.
message ObjectTransactionRequest {
    string lock_key = 1;               // object path to lock and to evaluate the condition against
    WriteCondition condition = 2;      // optional precondition, checked under the lock
    repeated ObjectMutation mutations = 3;
    bool is_from_other_cluster = 4;
    repeated int32 signatures = 5;
    string condition_key = 6;          // if set, evaluate the condition against this entry instead of lock_key (still locking lock_key)
    string route_key = 7;              // ring key identifying the owner filer; a non-owner forwards the whole transaction to it
    bool is_moved = 8;                 // set on a forwarded transaction so the receiver applies it locally instead of forwarding again
}

message ObjectTransactionResponse {
    string error = 1;
    FilerError error_code = 2;
}

// PosixLockRange is one advisory byte-range lock. Owner identity is (sid, owner):
// sid is the mount session, owner the FUSE lock owner within it, so owners from
// different mounts never alias. end is inclusive (max uint64 = to EOF); is_flock
// separates the flock and fcntl namespaces, which never conflict.
message PosixLockRange {
    uint64 start = 1;
    uint64 end = 2;
    uint32 type = 3;       // 1=read, 2=write, 3=unlock
    uint64 sid = 4;
    uint64 owner = 5;
    uint32 pid = 6;        // holder pid, for get_lk reporting only
    bool is_flock = 7;
}

// PosixLock routes an advisory lock operation to the inode's owner filer, which
// holds the authoritative in-memory lock table. key is the inode identity ring
// key (the file path, or hl:<HardLinkId> for a hardlink) used both to resolve the
// owner and to index the table. A non-owner filer forwards the request one hop;
// is_moved bounds it so a stale ring view cannot loop.
message PosixLockRequest {
    string key = 1;
    bool is_moved = 2;
    PosixLockOp op = 3;
    PosixLockRange lock = 4;
    // locks carries the full set a mount holds on key for a KEEP_ALIVE
    // re-assertion, so the current owner filer can rebuild its in-memory state
    // after an ownership change or restart. lock.sid identifies the session.
    repeated PosixLockRange locks = 5;
    // cooling_probe marks a dual-read a new owner sends to the previous owner
    // during a ring change, so the previous owner answers from local state
    // without itself cooling-off (no recursion).
    bool cooling_probe = 6;
}

enum PosixLockOp {
    TRY_LOCK = 0;             // grant lock or report conflict (non-blocking)
    UNLOCK = 1;              // release lock's owner's locks over its range
    GET_LK = 2;             // report a conflicting lock, if any
    RELEASE_POSIX_OWNER = 3; // drop the owner's fcntl locks (flush-time)
    RELEASE_FLOCK_OWNER = 4; // drop the owner's flock locks (release-time)
    KEEP_ALIVE = 5;         // renew the session's lease on this owner (lock.sid)
}

message PosixLockResponse {
    bool granted = 1;            // for TRY_LOCK: whether the lock was granted
    bool has_conflict = 2;       // whether conflict is populated
    PosixLockRange conflict = 3; // the blocking lock (TRY_LOCK conflict / GET_LK result)
}

// ObjectTransactionBatch applies several object transactions in one round trip,
// each under its own per-path lock and independent of the others (no cross-key
// atomicity). A caller groups keys that route to the same owner filer and sends
// one batch per owner, e.g. for a multi-object delete. Each response is parallel
// to its request.
message ObjectTransactionBatchRequest {
    repeated ObjectTransactionRequest transactions = 1;
}

message ObjectTransactionBatchResponse {
    repeated ObjectTransactionResponse responses = 1;
}

message CreateEntryResponse {
    string error = 1;                          // kept for human readability + backward compat
    SubscribeMetadataResponse metadata_event = 2;
    FilerError error_code = 3;                 // machine-readable error code
    // filer log position stamped under the path lock before the write:
    // every event at or below it is reflected in the acknowledged state
    int64 log_ts_ns = 4;
    int32 log_signature = 5; // filer whose clock stamped log_ts_ns
}

message UpdateEntryRequest {
    string directory = 1;
    Entry entry = 2;
    bool is_from_other_cluster = 3;
    repeated int32 signatures = 4;
    map<string, bytes> expected_extended = 5;
    // Optional precondition evaluated against the current entry atomically with
    // the write, under the filer's per-path lock. The caller must route the
    // key's writes to this entry's owner filer for the check to be authoritative.
    WriteCondition condition = 6;
}
message UpdateEntryResponse {
    SubscribeMetadataResponse metadata_event = 1;
    // filer log position stamped under the path lock before the write:
    // every event at or below it is reflected in the acknowledged state
    int64 log_ts_ns = 2;
    int32 log_signature = 3; // filer whose clock stamped log_ts_ns
}

message TouchAccessTimeRequest {
    string directory = 1;
    string name = 2;
    int64 client_atime_ns = 3; // nanoseconds since epoch; filer may override with relatime
}
message TouchAccessTimeResponse {
    int64 persisted_atime_ns = 1; // nanoseconds since epoch; 0 if no update was performed
    bool updated = 2;
}

message AppendToEntryRequest {
    string directory = 1;
    string entry_name = 2;
    repeated FileChunk chunks = 3;
}
message AppendToEntryResponse {
}

message DeleteEntryRequest {
    string directory = 1;
    string name = 2;
    // bool is_directory = 3;
    bool is_delete_data = 4;
    bool is_recursive = 5;
    bool ignore_recursive_error = 6;
    bool is_from_other_cluster = 7;
    repeated int32 signatures = 8;
    int64 if_not_modified_after = 9;
}

message DeleteEntryResponse {
    string error = 1;
    SubscribeMetadataResponse metadata_event = 2;
}

message AtomicRenameEntryRequest {
    string old_directory = 1;
    string old_name = 2;
    string new_directory = 3;
    string new_name = 4;
    repeated int32 signatures = 5;
}

message AtomicRenameEntryResponse {
}

message StreamRenameEntryRequest {
    string old_directory = 1;
    string old_name = 2;
    string new_directory = 3;
    string new_name = 4;
    repeated int32 signatures = 5;
}
message StreamRenameEntryResponse {
    string directory = 1;
    EventNotification event_notification = 2;
    int64 ts_ns = 3;
}
message AssignVolumeRequest {
    int32 count = 1;
    string collection = 2;
    string replication = 3;
    int32 ttl_sec = 4;
    string data_center = 5;
    string path = 6;
    string rack = 7;
    string data_node = 9;
    string disk_type = 8;
    uint64 expected_data_size = 10; // hint for size-aware volume selection
}

message AssignVolumeResponse {
    string file_id = 1;
    int32 count = 4;
    string auth = 5;
    string collection = 6;
    string replication = 7;
    string error = 8;
    Location location = 9;
    repeated Location replicas = 10;
    // fsync is the storage rule's fsync decision for the assigned path, so the
    // client can carry it onto the volume server upload request.
    bool fsync = 11;
}

message LookupVolumeRequest {
    repeated string volume_ids = 1;
}

message Locations {
    repeated Location locations = 1;
}

message Location {
    string url = 1;
    string public_url = 2;
    uint32 grpc_port = 3;
    string data_center = 4;
    bool data_in_remote = 5;
}
message LookupVolumeResponse {
    map<string, Locations> locations_map = 1;
}

message Collection {
    string name = 1;
}
message CollectionListRequest {
    bool include_normal_volumes = 1;
    bool include_ec_volumes = 2;
}
message CollectionListResponse {
    repeated Collection collections = 1;
}
message DeleteCollectionRequest {
    string collection = 1;
}

message DeleteCollectionResponse {
}

message StatisticsRequest {
    string replication = 1;
    string collection = 2;
    string ttl = 3;
    string disk_type = 4;
}
message StatisticsResponse {
    uint64 total_size = 4;
    uint64 used_size = 5;
    uint64 file_count = 6;
    // sizes counting one copy of the data, as reported by the master
    uint64 logical_total_size = 7;
    uint64 logical_used_size = 8;
}

message PingRequest {
    string target = 1; // default to ping itself
    string target_type = 2;
}
message PingResponse {
    int64 start_time_ns = 1;
    int64 remote_time_ns = 2;
    int64 stop_time_ns = 3;
}

message GetFilerConfigurationRequest {
}
message GetFilerConfigurationResponse {
    repeated string masters = 1;
    string replication = 2;
    string collection = 3;
    uint32 max_mb = 4;
    string dir_buckets = 5;
    bool cipher = 7;
    int32 signature = 8;
    string metrics_address = 9;
    int32 metrics_interval_sec = 10;
    string version = 11;
    string cluster_id = 12;
    string filer_group = 13;
    int32 major_version = 14;
    int32 minor_version = 15;
}

message SubscribeMetadataRequest {
    string client_name = 1;
    string path_prefix = 2;
    int64 since_ns = 3;
    int32 signature = 4;
    repeated string path_prefixes = 6;
    int32 client_id = 7;
    int64 until_ns = 8;
    int32 client_epoch = 9;
    repeated string directories = 10;  // exact directory to watch
    bool client_supports_batching = 11; // client can unpack SubscribeMetadataResponse.events
    bool client_supports_metadata_chunks = 12; // client can read log file chunks from volume servers
    bool client_supports_idle_heartbeat = 13; // server may send empty responses carrying the current time while the client is caught up
}
message SubscribeMetadataResponse {
    string directory = 1;
    EventNotification event_notification = 2;
    int64 ts_ns = 3;
    repeated SubscribeMetadataResponse events = 4; // batch of additional events (backlog catch-up)
    repeated LogFileChunkRef log_file_refs = 5;    // log file chunk refs for client direct-read
    int64 flushed_ts_ns = 6; // local log-buffer flush watermark: everything at or below it is on disk
}
message ListMetadataSubscribersRequest {
    repeated string client_types = 1; // optional filter by client type, e.g. "mount"; empty = all
}
message ListMetadataSubscribersResponse {
    repeated MetadataSubscriber subscribers = 1;
}
message MetadataSubscriber {
    string client_name = 1;     // "<type>@<address>"
    string client_type = 2;     // e.g. "mount", "sw-vfs", "s3", "filer:<addr>"
    string address = 3;         // client peer address
    string path_prefix = 4;     // subscribed path prefix
    int32 client_id = 5;
    int32 client_epoch = 6;
    int64 connected_at_ns = 7;
    string filer_address = 8;   // the filer this subscriber is connected to
}
// A persisted log file that the client can read directly from volume servers.
// The file format is: [4-byte size | protobuf LogEntry] repeated.
// Each LogEntry.Data contains a marshaled SubscribeMetadataResponse.
message LogFileChunkRef {
    repeated FileChunk chunks = 1; // chunk references (fids) to read from volume servers
    int64 file_ts_ns = 2;         // minute-level timestamp of the log file
    string filer_id = 3;          // filer signature suffix from log filename
}

message TraverseBfsMetadataRequest {
    string directory = 1;
    repeated string excluded_prefixes = 2;
}
message TraverseBfsMetadataResponse {
    string directory = 1;
    Entry entry = 2;
}

message LogEntry {
    int64 ts_ns = 1;
    int32 partition_key_hash = 2;
    bytes data = 3;
    bytes key = 4;
    int64 offset = 5;  // Sequential offset within partition
}

message KeepConnectedRequest {
    string name = 1;
    uint32 grpc_port = 2;
    repeated string resources = 3;
}
message KeepConnectedResponse {
}

message LocateBrokerRequest {
    string resource = 1;
}

message LocateBrokerResponse {
    bool found = 1;
    // if found, send the exact address
    // if not found, send the full list of existing brokers
    message Resource {
        string grpc_addresses = 1;
        int32 resource_count = 2;
    }
    repeated Resource resources = 2;
}

/////////////////////////
// Key-Value operations
/////////////////////////
message KvGetRequest {
    bytes key = 1;
}
message KvGetResponse {
    bytes value = 1;
    string error = 2;
}
message KvPutRequest {
    bytes key = 1;
    bytes value = 2;
}
message KvPutResponse {
    string error = 1;
}

/////////////////////////
// path-based configurations
/////////////////////////
message FilerConf {
    int32 version = 1;
    message PathConf {
        string location_prefix = 1;
        string collection = 2;
        string replication = 3;
        string ttl = 4;
        string disk_type = 5;
        bool fsync = 6;
        uint32 volume_growth_count = 7;
        bool read_only = 8;
        string data_center = 9;
        string rack = 10;
        string data_node = 11;
        uint32 max_file_name_length = 12;
        bool disable_chunk_deletion = 13;
        // unset inherits from the enclosing path rule, set overrides it
        optional bool worm = 14;
        uint64 worm_grace_period_seconds = 15;
        uint64 worm_retention_time_seconds = 16;
    }
    repeated PathConf locations = 2;
}

/////////////////////////
// Remote Storage related
/////////////////////////
message CacheRemoteObjectToLocalClusterRequest {
    string directory = 1;
    string name = 2;
    int32 chunk_concurrency = 3; // parallel chunk downloads per file, 0 = default (8)
    int32 download_concurrency = 4; // multipart download concurrency per chunk (if supported by remote storage), 0 = default (5 for S3)
}
message CacheRemoteObjectToLocalClusterResponse {
    Entry entry = 1;
    SubscribeMetadataResponse metadata_event = 2;
    // filer log position stamped before the entry read: every event at or
    // below it is reflected in the returned entry
    int64 log_ts_ns = 3;
    int32 log_signature = 4; // filer whose clock stamped log_ts_ns
}

/////////////////////////
// distributed lock management
/////////////////////////
message LockRequest {
    string name = 1;
    int64 seconds_to_lock = 2;
    string renew_token = 3;
    bool is_moved = 4;
    string owner = 5;
}
message LockResponse {
    string renew_token = 1;
    string lock_owner = 2;
    string lock_host_moved_to = 3;
    string error = 4;
    int64 generation = 5;
}
message UnlockRequest {
    string name = 1;
    string renew_token = 2;
    bool is_moved = 3;
}
message UnlockResponse {
    string error = 1;
    string moved_to = 2;
}
message FindLockOwnerRequest {
    string name = 1;
    bool is_moved = 2;
}
message FindLockOwnerResponse {
    string owner = 1;
}
message Lock {
    string name = 1;
    string renew_token = 2;
    int64 expired_at_ns = 3;
    string owner = 4;
    int64 generation = 5;
    bool is_backup = 6;
    int64 seq = 7;
}
message TransferLocksRequest {
    repeated Lock locks = 1;
}
message TransferLocksResponse {
}
message ReplicateLockRequest {
    string name = 1;
    string renew_token = 2;
    int64 expired_at_ns = 3;
    string owner = 4;
    int64 generation = 5;
    bool is_unlock = 6;
    int64 seq = 7;
}
message ReplicateLockResponse {
}

//////////////////////////////////////////////////
// StreamMutateEntry: ordered bidirectional streaming for all filer mutations.
// All create/update/delete/rename operations from a single mount go through
// one stream, preserving mutation ordering and eliminating per-request
// connection overhead.

message StreamMutateEntryRequest {
    uint64 request_id = 1;
    oneof request {
        CreateEntryRequest create_request = 2;
        UpdateEntryRequest update_request = 3;
        DeleteEntryRequest delete_request = 4;
        StreamRenameEntryRequest rename_request = 5;
    }
}

message StreamMutateEntryResponse {
    uint64 request_id = 1;
    bool is_last = 2; // always true except for rename, which sends multiple events
    oneof response {
        CreateEntryResponse create_response = 3;
        UpdateEntryResponse update_response = 4;
        DeleteEntryResponse delete_response = 5;
        StreamRenameEntryResponse rename_response = 6;
    }
    string error = 7; // human-readable error message when the operation failed
    int32 errno = 8;  // POSIX errno (e.g. ENOENT=2, ENOTEMPTY=66) for direct FUSE status mapping
}

//////////////////////////////////////////////////
// Peer chunk sharing — mount-server registry
//////////////////////////////////////////////////

message MountRegisterRequest {
    string peer_addr = 1;    // host:port where this mount serves peer chunk requests
    string rack = 2;         // locality label (rack); used for peer ranking
    int32 ttl_seconds = 3;   // how long the filer should keep this entry without a heartbeat
    string data_center = 4;  // locality label (data center); coarser than rack
}

message MountRegisterResponse {
}

message MountListRequest {
}

message MountListResponse {
    repeated MountInfo mounts = 1;
}

message MountInfo {
    string peer_addr = 1;
    string rack = 2;
    int64 last_seen_ns = 3;
    string data_center = 4;
}

// LookupDirectoryEntriesRequest batches independent exact-path lookups into
// one bounded RPC. Results preserve this order. Empty batches and batches over
// 4096 requests are rejected. Declared last to keep generated message indices
// stable.
message LookupDirectoryEntriesRequest {
    repeated LookupDirectoryEntryRequest requests = 1;
    // Cache callers may treat an Entry whose Volume the master no longer
    // reports as a miss. A lookup the filer could not complete still marks the
    // Entry with an error. Ordinary filesystem callers keep the fail-closed
    // error in both cases.
    bool unavailable_volume_is_miss = 2;
}

message LookupDirectoryEntryResult {
    // found reports metadata presence. An error makes this item unusable even
    // when entry is present, for example when a chunk volume cannot be located.
    bool found = 1;
    Entry entry = 2;
    string error = 3;
    // Per-entry read fence. Every filer event at or below this position is
    // reflected in found/entry/error for this item.
    int64 log_ts_ns = 4;
    int32 log_signature = 5;
}

message LookupDirectoryEntriesResponse {
    repeated LookupDirectoryEntryResult results = 1;
    // One deduplicated location set for every volume referenced by returned
    // Entry.chunks. A missing/unavailable volume has an empty Locations value
    // and marks each affected result with an error.
    map<string, Locations> locations_map = 2;
    // Short-lived, exact-FID read capabilities. Direct data-plane clients must
    // present the matching token to a Volume instead of treating a shared
    // service credential as authority to read arbitrary needles.
    map<string, string> read_auth = 3;
}
