1+ syntax = "proto3" ;
2+ package authzed.api.materialize.v0 ;
3+
4+ import "authzed/api/v1/core.proto" ;
5+
6+ option go_package = "github.com/authzed/authzed-go/proto/authzed/api/materialize/v0" ;
7+ option java_package = "com.authzed.api.materialize.v0" ;
8+ option java_multiple_files = true ;
9+
10+ service WatchPermissionSetsService {
11+ // WatchPermissionSets returns a stream of changes to the sets which can be used to compute the watched permissions.
12+ //
13+ // WatchPermissionSets lets consumers achieve the same thing as WatchPermissions, but trades off a simpler usage model with
14+ // significantly lower computational requirements. Unlike WatchPermissions, this method returns changes to the sets of permissions,
15+ // rather than the individual permissions. Permission sets are a normalized form of the computed permissions, which
16+ // means that the consumer must perform an extra computation over this representation to obtain the final computed
17+ // permissions, typically by intersecting the provided sets.
18+ //
19+ // For example, this would look like a JOIN between the
20+ // materialize permission sets table in a target relation database, the table with the resources to authorize access
21+ // to, and the table with the subject (e.g. a user).
22+ //
23+ // In exchange, the number of changes issued by WatchPermissionSets will be several orders of magnitude less than those
24+ // emitted by WatchPermissions, which has several implications:
25+ // - significantly less resources to compute the sets
26+ // - significantly less messages to stream over the network
27+ // - significantly less events to ingest on the consumer side
28+ // - less ingestion lag from the origin SpiceDB mutation
29+ //
30+ // The type of scenarios WatchPermissionSets is particularly well suited is when a single change
31+ // in the origin SpiceDB can yield millions of changes. For example, in the GitHub authorization model, assigning a role
32+ // to a top-level team of an organization with hundreds of thousands of employees can lead to an explosion of
33+ // permission change events that would require a lot of computational resources to process, both on Materialize and
34+ // the consumer side.
35+ //
36+ // WatchPermissionSets is thus recommended for any larger scale use case where the fan-out in permission changes that
37+ // emerges from a specific schema and data shape is too large to handle effectively.
38+ //
39+ // The API does not offer a sharding mechanism and thus there should only be one consumer per target system.
40+ // Implementing an active-active HA consumer setup over the same target system will require coordinating which
41+ // revisions have been consumed in order to prevent transitioning to an inconsistent state.
42+ rpc WatchPermissionSets (WatchPermissionSetsRequest ) returns (stream WatchPermissionSetsResponse ) {}
43+
44+ // LookupPermissionSets returns the current state of the permission sets which can be used to derive the computed permissions.
45+ // It's typically used to backfill the state of the permission sets in the consumer side.
46+ //
47+ // It's a cursored API and the consumer is responsible to keep track of the cursor and use it on each subsequent call.
48+ // Each stream will return <N> permission sets defined by the specified request limit. The server will keep streaming until
49+ // the sets per stream is hit, or the current state of the sets is reached,
50+ // whatever happens first, and then close the stream. The server will indicate there are no more changes to stream
51+ // through the `completed_members` in the cursor.
52+ //
53+ // There may be many elements to stream, and so the consumer should be prepared to resume the stream from the last
54+ // cursor received. Once completed, the consumer may start streaming permission set changes using WatchPermissionSets
55+ // and the revision token from the last LookupPermissionSets response.
56+ rpc LookupPermissionSets (LookupPermissionSetsRequest ) returns (stream LookupPermissionSetsResponse ) {}
57+ }
58+
59+ message WatchPermissionSetsRequest {
60+ // optional_starting_after is used to specify the SpiceDB revision to start watching from.
61+ // If not specified, the watch will start from the current SpiceDB revision time of the request ("head revision").
62+ authzed.api.v1.ZedToken optional_starting_after = 1 ;
63+ }
64+
65+ message WatchPermissionSetsResponse {
66+ oneof response {
67+ // change is the permission set delta that has occurred as result of a mutation in origin SpiceDB.
68+ // The consumer should apply this change to the current state of the permission sets in their target system.
69+ // Once an event arrives with completed_revision instead, the consumer shall consider the set of
70+ // changes originating from that revision completed.
71+ //
72+ // The consumer should keep track of the revision in order to resume streaming in the event of consumer restarts.
73+ PermissionSetChange change = 1 ;
74+
75+ // completed_revision is the revision token that indicates the completion of a set of changes. It may also be
76+ // received without accompanying set of changes, indicating that a mutation in the origin SpiceDB cluster did
77+ // not yield any effective changes in the permission sets
78+ authzed.api.v1.ZedToken completed_revision = 2 ;
79+
80+ // lookup_permission_sets_required is a signal that the consumer should perform a LookupPermissionSets call because
81+ // the permission set snapshot needs to be rebuilt from scratch. This typically happens when the origin SpiceDB
82+ // cluster has seen its schema changed.
83+ LookupPermissionSetsRequired lookup_permission_sets_required = 3 ;
84+ }
85+ }
86+
87+ message Cursor {
88+ // limit is the number of permission sets to stream over a single LookupPermissionSets call that was requested.
89+ uint32 limit = 1 ;
90+ // token is the snapshot revision at which the cursor was computed.
91+ authzed.api.v1.ZedToken token = 4 ;
92+ // starting_index is an offset of the permission set represented by this cursor
93+ uint32 starting_index = 5 ;
94+ // completed_members is a boolean flag that indicates that the cursor has reached the end of the permission sets
95+ bool completed_members = 6 ;
96+ }
97+
98+ message LookupPermissionSetsRequest {
99+ // limit is the number of permission sets to stream over a single LookupPermissionSets. Once the limit is reached,
100+ // the server will close the stream. If more permission sets are available, the consume should open a new stream
101+ // providing optional_starting_after_cursor, using the cursor from the last response.
102+ uint32 limit = 1 ;
103+ // optional_starting_after_cursor is used to specify the offset to start streaming permission sets from.
104+ Cursor optional_starting_after_cursor = 4 ;
105+ }
106+
107+ message LookupPermissionSetsResponse {
108+ // change represents the permission set delta necessary to transition an uninitialized target system to
109+ // a specific snapshot revision. In practice it's not different from the WatchPermissionSetsResponse.change, except
110+ // all changes will be of time SET_OPERATION_ADDED because it's assumed there is no known previous state.
111+ //
112+ // Applying the deltas to a previously initialized target system would yield incorrect results.
113+ PermissionSetChange change = 1 ;
114+ // cursor points to a specific permission set in a revision.
115+ // The consumer should keep track of the cursor in order to resume streaming in the event of consumer restarts. This
116+ // is particularly important in backfill scenarios that may take hours or event days to complete.
117+ Cursor cursor = 2 ;
118+ }
119+
120+ message PermissionSetChange {
121+ enum SetOperation {
122+ SET_OPERATION_UNSPECIFIED = 0 ;
123+ SET_OPERATION_ADDED = 1 ;
124+ SET_OPERATION_REMOVED = 2 ;
125+ }
126+
127+ // revision represents the revision at which the permission set change occurred.
128+ authzed.api.v1.ZedToken at_revision = 1 ;
129+ // operation represents the type of set operation that took place as part of the change
130+ SetOperation operation = 2 ;
131+ // parent_set represents the permission set parent of either another set or a member
132+ SetReference parent_set = 3 ;
133+
134+ oneof child {
135+ // child_set represents the scenario where another set is considered member of the parent set
136+ SetReference child_set = 4 ;
137+ // child_member represents the scenario where an specific object is considered member of the parent set
138+ MemberReference child_member = 5 ;
139+ }
140+ }
141+
142+ message SetReference {
143+ // object_type is the type of object in a permission set
144+ string object_type = 1 ;
145+ // object_id is the ID of a permission set
146+ string object_id = 2 ;
147+ // permission_or_relation is the permission or relation referenced by this permission set
148+ string permission_or_relation = 3 ;
149+ }
150+
151+ message MemberReference {
152+ // object_type is the type of object of a permission set member
153+ string object_type = 1 ;
154+ // object_id is the ID of a permission set member
155+ string object_id = 2 ;
156+ // optional_permission_or_relation is the permission or relation referenced by this permission set member
157+ string optional_permission_or_relation = 3 ;
158+ }
159+
160+ // LookupPermissionSetsRequired is a signal that the consumer should perform a LookupPermissionSets call because
161+ // the permission set snapshot needs to be rebuilt from scratch. This typically happens when the origin SpiceDB
162+ // cluster has seen its schema changed.
163+ message LookupPermissionSetsRequired {
164+ // required_lookup_at is the snapshot revision at which the permission set needs to be rebuilt to.
165+ authzed.api.v1.ZedToken required_lookup_at = 1 ;
166+ }
0 commit comments