Skip to content

Commit 05bb244

Browse files
committed
feat(api): add governance query operation
1 parent b70d40e commit 05bb244

13 files changed

Lines changed: 1652 additions & 760 deletions

File tree

api/spec/packages/aip/src/common/pagination.tsp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,34 @@ using TypeSpec.OpenAPI;
88
namespace Common;
99

1010
/**
11-
* Cursor page query.
11+
* Determines which page of the collection to retrieve.
1212
*/
13-
@friendlyName("CursorPaginationQuery")
14-
model CursorPaginationQuery {
13+
@friendlyName("CursorPaginationQueryPage")
14+
model CursorPaginationQueryPage {
1515
/**
16-
* Determines which page of the collection to retrieve.
16+
* Maximum number of customer rows to include in the response.
1717
*/
18-
@query(#{ explode: true, style: "deepObject" })
19-
page?: {
20-
/**
21-
* The number of items to include per page.
22-
*/
23-
size?: integer;
18+
size?: integer;
2419

25-
/**
26-
* Request the next page of data, starting with the item after this parameter.
27-
*/
28-
after?: string;
20+
/**
21+
* Request the next page of data, starting with the item after this parameter.
22+
*/
23+
after?: string;
2924

30-
/**
31-
* Request the previous page of data, starting with the item before this parameter.
32-
*/
33-
before?: string;
34-
};
25+
/**
26+
* Request the previous page of data, starting with the item before this parameter.
27+
*/
28+
before?: string;
29+
}
30+
31+
/**
32+
* Cursor page query.
33+
*/
34+
@friendlyName("CursorPaginationQuery")
35+
model CursorPaginationQuery {
36+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
37+
@query(#{ explode: true, style: "deepObject" })
38+
page?: CursorPaginationQueryPage;
3539
}
3640

3741
/**
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import "../shared/index.tsp";
2+
import "../common/pagination.tsp";
3+
import "../customers/customer.tsp";
4+
5+
namespace Governance;
6+
7+
/**
8+
* List of customer identifiers to evaluate access for.
9+
*/
10+
@friendlyName("GovernanceQueryCustomersFilter")
11+
model GovernanceQueryCustomersFilter {
12+
/**
13+
* Each entry can be a customer `key` or a usage-attribution subject `key`.
14+
* Identifiers that cannot be resolved to a customer are reported in the response
15+
* `errors` array.
16+
*/
17+
@summary("Customer usage attribution keys")
18+
@minItems(1)
19+
@maxItems(1000)
20+
keys: string[];
21+
}
22+
23+
/**
24+
* Optional list of feature keys to evaluate access for. If omitted, all features
25+
* available in the organization are returned. Providing this list is recommended
26+
* to reduce the response size and the load on the backend services.
27+
*/
28+
@friendlyName("GovernanceQueryFeaturesFilter")
29+
model GovernanceQueryFeaturesFilter {
30+
/**
31+
* List of feature keys to evaluate access for.
32+
*/
33+
@summary("Feature Keys")
34+
@minItems(1)
35+
@maxItems(1000)
36+
keys: string[];
37+
}
38+
39+
/**
40+
* Filters to apply to the governance query.
41+
*/
42+
@friendlyName("GovernanceQueryFilters")
43+
model GovernanceQueryFilters {
44+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
45+
customers: GovernanceQueryCustomersFilter;
46+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
47+
features?: GovernanceQueryFeaturesFilter;
48+
49+
/**
50+
* Whether to include credit balance availability for each resolved customer. When
51+
* true, each result entry includes `has_available_credits`.
52+
*
53+
* Defaults to `false`.
54+
*/
55+
@visibility(Lifecycle.Create)
56+
@summary("Include credits")
57+
include_credits?: boolean = false;
58+
}
59+
60+
/**
61+
* Query to evaluate feature access for a list of customers.
62+
*/
63+
@friendlyName("GovernanceQueryRequest")
64+
model GovernanceQueryRequest {
65+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
66+
filters: GovernanceQueryFilters;
67+
}
68+
69+
/**
70+
* Response of the governance query.
71+
*/
72+
@friendlyName("GovernanceQueryResponse")
73+
model GovernanceQueryResponse {
74+
/**
75+
* Access evaluation results, one entry per resolved customer.
76+
*/
77+
@visibility(Lifecycle.Read)
78+
@summary("Data")
79+
data: GovernanceQueryResult[];
80+
81+
/**
82+
* Partial errors encountered while processing the request.
83+
*/
84+
@visibility(Lifecycle.Read)
85+
@summary("Errors")
86+
errors: GovernanceQueryError[];
87+
88+
/**
89+
* Pagination metadata. The endpoint may return a partial response if the full
90+
* response would exceed server-side limits.
91+
*/
92+
@visibility(Lifecycle.Read)
93+
@summary("Meta")
94+
meta: Common.CursorMeta;
95+
}
96+
97+
/**
98+
* Access evaluation result for a single resolved customer.
99+
*/
100+
@friendlyName("GovernanceQueryResult")
101+
model GovernanceQueryResult {
102+
/**
103+
* The list of identifiers from the request that resolved to this customer. Each
104+
* entry is either the customer `key` or one of its usage-attribution subject
105+
* `key`s.
106+
*/
107+
@visibility(Lifecycle.Read)
108+
@summary("Matched Identifiers")
109+
matched: string[];
110+
111+
/**
112+
* The customer the matched identifiers resolved to.
113+
*/
114+
@visibility(Lifecycle.Read)
115+
@summary("Customer")
116+
customer: Customers.Customer;
117+
118+
/**
119+
* Whether the customer has any available prepaid credit balance.
120+
*
121+
* Only populated when the request was made with `include_credits=true`.
122+
*/
123+
@visibility(Lifecycle.Read)
124+
@summary("Has available credits")
125+
has_available_credits?: boolean;
126+
127+
/**
128+
* Map of features with their access status.
129+
*
130+
* Map keys are the feature keys requested in `filters.features.keys`, or every
131+
* feature `key` available in the organization when the feature filter was omitted.
132+
*/
133+
@visibility(Lifecycle.Read)
134+
@summary("Features")
135+
features: Record<GovernanceFeatureAccess>;
136+
137+
/**
138+
* Timestamp of the most recent change to the customer's access state reflected in
139+
* this result.
140+
*/
141+
@visibility(Lifecycle.Read)
142+
@summary("Updated at")
143+
updated_at: Shared.DateTime;
144+
}
145+
146+
/**
147+
* Access status for a single feature.
148+
*/
149+
@friendlyName("GovernanceFeatureAccess")
150+
model GovernanceFeatureAccess {
151+
/**
152+
* Whether the customer currently has access to the feature.
153+
*
154+
* `true` for boolean and static entitlements that are available, and for metered
155+
* entitlements with remaining balance. `false` when the feature is unavailable,
156+
* the usage limit has been reached, or (when applicable) credits have been
157+
* exhausted.
158+
*/
159+
@visibility(Lifecycle.Read)
160+
@summary("Has access")
161+
has_access: boolean;
162+
163+
/**
164+
* Optional reason when the customer does not have access to the feature. Populated
165+
* when `has_access` is `false`.
166+
*/
167+
@visibility(Lifecycle.Read)
168+
@summary("Denied reason")
169+
denied_reason?: GovernanceFeatureAccessDeniedReason;
170+
}
171+
172+
/**
173+
* Reason a feature is not accessible to a customer.
174+
*/
175+
@friendlyName("GovernanceFeatureAccessDeniedReason")
176+
model GovernanceFeatureAccessDeniedReason
177+
is Shared.BaseError<GovernanceFeatureAccessDeniedReasonCode>;
178+
179+
/**
180+
* Machine-readable reason code for denied feature access.
181+
*/
182+
@friendlyName("GovernanceFeatureAccessDeniedReasonCode")
183+
enum GovernanceFeatureAccessDeniedReasonCode {
184+
/**
185+
* Default zero value. Reserved for forward compatibility.
186+
*/
187+
Unknown: "unknown",
188+
189+
/**
190+
* The customer has reached the metered usage limit for the feature
191+
* within the current usage period.
192+
*/
193+
UsageLimitReached: "usage_limit_reached",
194+
195+
/**
196+
* The feature is not available to the customer.
197+
*/
198+
FeatureUnavailable: "feature_unavailable",
199+
200+
/**
201+
* The feature `key` referenced by the request is not configured in the
202+
* organization.
203+
*/
204+
FeatureNotFound: "feature_not_found",
205+
206+
/**
207+
* The customer has no available prepaid credit balance.
208+
*/
209+
NoCreditAvailable: "no_credit_available",
210+
}
211+
212+
/**
213+
* Query error within a partially successful governance query response.
214+
*/
215+
@friendlyName("GovernanceQueryError")
216+
model GovernanceQueryError is Shared.BaseError<GovernanceQueryErrorCode> {
217+
/**
218+
* The customer identifier from the request that produced this error.
219+
*/
220+
@visibility(Lifecycle.Read)
221+
@summary("Customer Identifier")
222+
customer?: string;
223+
}
224+
225+
/**
226+
* Error code for a governance query failure.
227+
*/
228+
@friendlyName("GovernanceQueryErrorCode")
229+
enum GovernanceQueryErrorCode {
230+
/**
231+
* Default zero value. Reserved for forward compatibility.
232+
*/
233+
Unknown: "unknown",
234+
235+
/**
236+
* The provided identifier could not be resolved to any customer
237+
* (neither by customer `key` nor by a usage-attribution subject `key`).
238+
*/
239+
CustomerNotFound: "customer_not_found",
240+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import "./governance.tsp";
2+
import "./operations.tsp";
3+
4+
namespace Governance;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import "@typespec/http";
2+
import "@typespec/rest";
3+
import "@typespec/openapi";
4+
import "@typespec/openapi3";
5+
import "../common/error.tsp";
6+
import "../shared/index.tsp";
7+
import "./governance.tsp";
8+
9+
using TypeSpec.Http;
10+
using TypeSpec.OpenAPI;
11+
12+
namespace Governance;
13+
14+
interface GovernanceOperations {
15+
/**
16+
* Query feature access for a list of customers.
17+
*
18+
* The endpoint resolves each provided identifier to a customer and returns the
19+
* access status for the requested features, plus optional credit balance
20+
* availability.
21+
*
22+
* _Designed to be called on a fixed refresh interval and the query response is
23+
* intended to be cached._
24+
*/
25+
@extension(Shared.UnstableExtension, true)
26+
@extension(Shared.InternalExtension, true)
27+
@post
28+
@route("/query")
29+
@operationId("query-governance-access")
30+
@summary("Query governance access")
31+
query(
32+
...Common.CursorPaginationQuery,
33+
@body _: GovernanceQueryRequest,
34+
): GovernanceQueryResponse | Common.ErrorResponses;
35+
}

api/spec/packages/aip/src/konnect.tsp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import "./apps/index.tsp";
1313
import "./billing/index.tsp";
1414
import "./tax/index.tsp";
1515
import "./defaults/index.tsp";
16+
import "./governance/index.tsp";
1617

1718
using TypeSpec.Http;
1819
using TypeSpec.OpenAPI;
@@ -54,6 +55,10 @@ using TypeSpec.OpenAPI;
5455
#{ description: Shared.ProductCatalogDescription }
5556
)
5657
@tagMetadata(Shared.DefaultsTag, #{ description: Shared.DefaultsDescription })
58+
@tagMetadata(
59+
Shared.GovernanceTag,
60+
#{ description: Shared.GovernanceDescription }
61+
)
5762
@useAuth(systemAccountAccessToken | personalAccessToken | konnectAccessToken)
5863
namespace MeteringAndBilling;
5964

@@ -183,8 +188,13 @@ interface PlanAddonEndpoints extends ProductCatalog.PlanAddonOperations {}
183188
interface OrganizationDefaultTaxCodesEndpoints
184189
extends Defaults.OrganizationDefaultTaxCodesOperations {}
185190

191+
@route("/openmeter/governance")
192+
@tag(Shared.GovernanceTag)
193+
interface GovernanceEndpoints extends Governance.GovernanceOperations {}
194+
186195
/**
187-
* The system account access token is meant for automations and integrations that are not directly associated with a human identity.
196+
* The system account access token is meant for automations and integrations that
197+
* are not directly associated with a human identity.
188198
*/
189199
#suppress "@openmeter/api-spec-aip/casing" "Use existing values"
190200
@friendlyName("systemAccountAccessToken")
@@ -194,7 +204,8 @@ model systemAccountAccessToken {
194204
}
195205

196206
/**
197-
* The personal access token is meant to be used as an alternative to basic-auth when accessing Konnect via APIs.
207+
* The personal access token is meant to be used as an alternative to basic-auth
208+
* when accessing Konnect via APIs.
198209
*/
199210
#suppress "@openmeter/api-spec-aip/casing" "Use existing values"
200211
@friendlyName("personalAccessToken")
@@ -204,7 +215,8 @@ model personalAccessToken {
204215
}
205216

206217
/**
207-
* The Konnect access token is meant to be used by the Konnect dashboard and the decK CLI authenticate with.
218+
* The Konnect access token is meant to be used by the Konnect dashboard and the
219+
* decK CLI authenticate with.
208220
*/
209221
#suppress "@openmeter/api-spec-aip/casing" "Use existing values"
210222
@friendlyName("konnectAccessToken")

0 commit comments

Comments
 (0)