Subscriptions Protobuf Subscriptions Protobuf (Activity v1alpha)

    Overview

      This document specifies the Subscriptions gRPC service that lets clients subscribe and unsubscribe to document paths within an account, and list active subscriptions. Subscriptions can be recursive (covering an entire directory) and may be created asynchronously to avoid blocking on initial sync.

    Goals

      Provide a minimal API to subscribe/unsubscribe to resources identified by (account, path).

      Support recursive subscriptions to monitor entire directories.

      Offer pagination for listing active subscriptions.

      Allow async fire‑and‑forget creation to decouple UI from initial sync.

    Non‑Goals

      Delivering the event stream itself (handled by Activity/Feed or Sync layers).

      Authorization policy definition (capability checks are enforced elsewhere).

      Batch subscribe/unsubscribe (can be added later).

    Protocol Definition

    syntax = "proto3";
    
    package com.seed.activity.v1alpha;
    
    import "google/protobuf/timestamp.proto";
    import "google/protobuf/empty.proto";
    
    option go_package = "seed/backend/genproto/activity/v1alpha;activity";
    
    // Subscriptions service provides subscription capabilities.
    service Subscriptions {
      // Subscribe to a document or space.
      rpc Subscribe(SubscribeRequest) returns (google.protobuf.Empty);
    
      // Remove a subscription.
      rpc Unsubscribe(UnsubscribeRequest) returns (google.protobuf.Empty);
    
      // Lists active subscriptions.
      rpc ListSubscriptions(ListSubscriptionsRequest) returns (ListSubscriptionsResponse);
    }
    
    // Subscribe to a resource
    message SubscribeRequest {
      // Required. The ID of the account where the subscribed document is located.
      string account = 1;
    
      // Required. Path of the document.
      // Empty string means root document.
      string path = 2;
    
      // Optional. Indicate if we not only subscribe to the resource
      // ID above but also to all documents on its directory.
      bool recursive = 3;
    
      // Optional. If true, the server will not wait for the subscription
      // to be synced for the first time before returning.
      optional bool async = 4;
    }
    
    // Subscribe to a resource
    message UnsubscribeRequest {
      // Required. The ID of the account where the subscribed document is located.
      string account = 1;
    
      // Required. Path of the document.
      // Empty string means root document.
      string path = 2;
    }
    
    // Get a list of active subscriptions.
    message ListSubscriptionsRequest {
      // Optional. The size of the page. The default is defined by the server.
      int32 page_size = 1;
    
      // Optional. The page token for requesting next pages.
      string page_token = 2;
    }
    
    // Get a list of active subscriptions.
    message ListSubscriptionsResponse {
      // The list of subscriptions.
      repeated Subscription subscriptions = 1;
    
      // The token to request the next page.
      string next_page_token = 2;
    }
    
    // Description of the subscription item.
    message Subscription {
      // Account to which the document belongs.
      string account = 1;
    
      // Path of the document within the account.
      // Empty string means root document.
      string path = 2;
    
      // Whether this subscription also subscribes to
      // all documents in the document's directory.
      bool recursive = 3;
    
      // Timestamp when the user started the subscrition.
      google.protobuf.Timestamp since = 4;
    }
    

    Service

    Subscriptions exposes three RPCs:

      Subscribe(SubscribeRequest) → Empty — Create (or idempotently ensure) a subscription for (account, path), optionally recursive and async.

      Unsubscribe(UnsubscribeRequest) → Empty — Remove the subscription for (account, path).

      ListSubscriptions(ListSubscriptionsRequest) → ListSubscriptionsResponse — Paginated listing of current subscriptions.

    All methods are idempotent: re‑subscribing to the same target or unsubscribing a non‑existent subscription should succeed with OK.

    Messages

    SubscribeRequest

      account (string, required) — Account that owns the document space.

      path (string, required) — Path in the account; "" denotes root.

      recursive (bool, optional) — If true, subscribe to the entire directory subtree.

      async (bool, optional) — If true, return immediately without waiting for initial sync.

    UnsubscribeRequest

      account (string, required) — Account id.

      path (string, required) — Path; "" for root.

    ListSubscriptionsRequest

      page_size (int32, optional) — Page size; server may cap.

      page_token (string, optional) — Opaque token to continue pagination.

    ListSubscriptionsResponse

      subscriptions (Subscription[]) — Page of subscriptions.

      next_page_token (string) — Present if more results exist.

    Subscription

      account, path — Subscription target.

      recursive — Whether it applies to a subtree.

      since — Creation timestamp.

    Field Semantics & Validation

      Path normalization — Servers MUST canonicalize paths; treat "" as root.

      Idempotence — Repeated Subscribe or Unsubscribe calls for the same target should be safe.

      Async behavior — When async=true, the server SHOULD enqueue subscription work and return immediately; otherwise it MAY block until the initial snapshot/sync is confirmed or a deadline is reached.

      Recursive semanticsrecursive=true applies to all documents under the directory prefix; implementations SHOULD guard against pathological fan‑out.

      Uniqueness — A subscription is uniquely identified by (account, path, recursive) or (account, path) depending on implementation; servers SHOULD document which dimensions define uniqueness.

    Errors

    Common gRPC status codes:

      INVALID_ARGUMENT — Malformed account or path; unsupported path.

      NOT_FOUNDUnsubscribe target not known (optional; may still return OK for idempotence).

      PERMISSION_DENIED — Caller lacks rights to subscribe to the target (e.g., no read capability).

      RESOURCE_EXHAUSTED — Subscription limit reached (per user/account/system).

      DEADLINE_EXCEEDED — Synchronous Subscribe timed out during initial sync.

      UNAVAILABLE — Sync subsystem temporarily unavailable.

      INTERNAL — Unexpected server error.

    Security & Permissions

      AuthZ check — Enforce read access to the target (account, path) prior to creating a subscription; recursive requests must verify subtree access.

      Privacy — Avoid revealing existence of private paths via error timing or detailed messages.

      Rate limiting — Apply quotas to prevent abuse (especially with recursive=true).

    Versioning Strategy

      Additive fields to Subscription and request messages are backwards compatible.

      Breaking changes require a new package (e.g., v1beta).

    Examples

    grpcurl — Subscribe (recursive)

    grpcurl \
      -H "Authorization: Bearer $TOKEN" \
      api.seed.hyper.media:443 com.seed.activity.v1alpha.Subscriptions.Subscribe \
      '{"account":"acc_1abc","path":"/docs","recursive":true}'
    

    grpcurl — Subscribe (async)

    grpcurl \
      -H "Authorization: Bearer $TOKEN" \
      api.seed.hyper.media:443 com.seed.activity.v1alpha.Subscriptions.Subscribe \
      '{"account":"acc_1abc","path":"/docs/roadmap","async":true}'
    

    grpcurl — Unsubscribe

    grpcurl \
      -H "Authorization: Bearer $TOKEN" \
      api.seed.hyper.media:443 com.seed.activity.v1alpha.Subscriptions.Unsubscribe \
      '{"account":"acc_1abc","path":"/docs"}'
    

    grpcurl — ListSubscriptions

    grpcurl \
      -H "Authorization: Bearer $TOKEN" \
      api.seed.hyper.media:443 com.seed.activity.v1alpha.Subscriptions.ListSubscriptions \
      '{"page_size":50}'
    

    Sample response (truncated):

    {
      "subscriptions": [
        {"account":"acc_1abc","path":"/docs","recursive":true,"since":"2025-10-14T15:00:00Z"},
        {"account":"acc_1abc","path":"/announcements","recursive":false,"since":"2025-10-10T12:30:00Z"}
      ],
      "next_page_token": "eyJvZmZzZXQiOjUwLCJjdHMiOiIyMDI1LTEwLTE0VDE1OjAwOjAwWiJ9"
    }
    

    Client Guidelines

      Prefer async=true for UI flows where initial sync may take time; poll Activity/Feed for updates.

      De‑duplicate local subscription state by (account, path) and reconcile with server list.

      Use pagination for large subscription sets and avoid hammering with frequent full listings.

    Server Guidelines

      Enforce idempotence and canonical storage keys for subscriptions.

      Respect caller capabilities; deny recursive subscriptions that exceed scope.

      Emit internal metrics (creation latency, fan‑out size, queue depth) for ops visibility.

    Future Work

      Batch operations: BatchSubscribe, BatchUnsubscribe.

      Filters for ListSubscriptionsRequest (by account, prefix, recursive flag).

      Watch stream for live subscription state changes.

      Ownership and labels on subscriptions for UX discoverability.

    Changelog

      2025‑10‑27: Second draft of Subscriptions service spec (Activity v1alpha).