Skip to content

Commit 617c9e6

Browse files
authored
feat(api): add governance query operation (#4329)
1 parent 5e858a7 commit 617c9e6

13 files changed

Lines changed: 1536 additions & 696 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+
* The number of items to include per page.
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: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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("GovernanceQueryRequestCustomers")
11+
model GovernanceQueryRequestCustomers {
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 keys and usage-attribution subjects")
18+
@minItems(1)
19+
@maxItems(100)
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("GovernanceQueryRequestFeatures")
29+
model GovernanceQueryRequestFeatures {
30+
/**
31+
* List of feature keys to evaluate access for.
32+
*/
33+
@summary("Feature Keys")
34+
@minItems(1)
35+
@maxItems(100)
36+
keys: string[];
37+
}
38+
39+
/**
40+
* Query to evaluate feature access for a list of customers.
41+
*/
42+
@friendlyName("GovernanceQueryRequest")
43+
model GovernanceQueryRequest {
44+
/**
45+
* Whether to include credit balance availability for each resolved customer. When
46+
* true, each feature evaluation includes credit balance checks.
47+
*
48+
* Defaults to `false`.
49+
*/
50+
@summary("Include credits")
51+
include_credits?: boolean = false;
52+
53+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
54+
@summary("Customer")
55+
customer: GovernanceQueryRequestCustomers;
56+
57+
#suppress "@openmeter/api-spec-aip/doc-decorator" "-"
58+
@summary("Feature")
59+
feature?: GovernanceQueryRequestFeatures;
60+
}
61+
62+
/**
63+
* Response of the governance query.
64+
*/
65+
@friendlyName("GovernanceQueryResponse")
66+
model GovernanceQueryResponse {
67+
/**
68+
* Access evaluation results, one entry per resolved customer.
69+
*/
70+
@visibility(Lifecycle.Read)
71+
@summary("Data")
72+
data: GovernanceQueryResult[];
73+
74+
/**
75+
* Partial errors encountered while processing the request.
76+
*/
77+
@visibility(Lifecycle.Read)
78+
@summary("Errors")
79+
errors: GovernanceQueryError[];
80+
81+
/**
82+
* Pagination metadata. The endpoint may return a partial response if the full
83+
* response would exceed server-side limits.
84+
*/
85+
@visibility(Lifecycle.Read)
86+
@summary("Meta")
87+
meta: Common.CursorMeta;
88+
}
89+
90+
/**
91+
* Access evaluation result for a single resolved customer.
92+
*/
93+
@friendlyName("GovernanceQueryResult")
94+
model GovernanceQueryResult {
95+
/**
96+
* The list of identifiers from the request that resolved to this customer. Each
97+
* entry is either the customer `key` or one of its usage-attribution subject
98+
* `key`s.
99+
*
100+
* Duplicate or aliased identifiers that resolve to the same customer collapse to a
101+
* single result entry, with every requested identifier listed here.
102+
*/
103+
@visibility(Lifecycle.Read)
104+
@summary("Matched identifiers")
105+
matched: string[];
106+
107+
/**
108+
* The customer the matched identifiers resolved to.
109+
*/
110+
@visibility(Lifecycle.Read)
111+
@summary("Customer")
112+
customer: Customers.Customer;
113+
114+
/**
115+
* Map of features with their access status.
116+
*
117+
* Map keys are the feature keys requested in `feature.keys`, or every feature
118+
* `key` available in the organization when the feature filter was omitted.
119+
*/
120+
@visibility(Lifecycle.Read)
121+
@summary("Features")
122+
features: Record<GovernanceFeatureAccess>;
123+
124+
/**
125+
* Timestamp of the most recent change to the customer's access state reflected in
126+
* this result.
127+
*/
128+
@visibility(Lifecycle.Read)
129+
@summary("Updated at")
130+
updated_at: Shared.DateTime;
131+
}
132+
133+
/**
134+
* Access status for a single feature.
135+
*/
136+
@friendlyName("GovernanceFeatureAccess")
137+
model GovernanceFeatureAccess {
138+
/**
139+
* Whether the customer currently has access to the feature.
140+
*
141+
* `true` for boolean and static entitlements that are available, and for metered
142+
* entitlements with remaining balance. `false` when the feature is unavailable,
143+
* the usage limit has been reached, or (when applicable) credits have been
144+
* exhausted.
145+
*/
146+
@visibility(Lifecycle.Read)
147+
@summary("Has access")
148+
has_access: boolean;
149+
150+
/**
151+
* Optional reason when the customer does not have access to the feature. Populated
152+
* when `has_access` is `false`.
153+
*/
154+
@visibility(Lifecycle.Read)
155+
@summary("Reason")
156+
reason?: GovernanceFeatureAccessReason;
157+
}
158+
159+
/**
160+
* Reason a feature is not accessible to a customer.
161+
*/
162+
@friendlyName("GovernanceFeatureAccessReason")
163+
model GovernanceFeatureAccessReason
164+
is Shared.BaseError<GovernanceFeatureAccessReasonCode>;
165+
166+
/**
167+
* Machine-readable reason code for denied feature access.
168+
*/
169+
@friendlyName("GovernanceFeatureAccessReasonCode")
170+
enum GovernanceFeatureAccessReasonCode {
171+
/**
172+
* Default zero value. Reserved for forward compatibility.
173+
*/
174+
Unknown: "unknown",
175+
176+
/**
177+
* The customer has reached the metered usage limit for the feature
178+
* within the current usage period.
179+
*/
180+
UsageLimitReached: "usage_limit_reached",
181+
182+
/**
183+
* The feature is not available to the customer.
184+
*/
185+
FeatureUnavailable: "feature_unavailable",
186+
187+
/**
188+
* The feature `key` referenced by the request is not configured in the
189+
* organization.
190+
*/
191+
FeatureNotFound: "feature_not_found",
192+
193+
/**
194+
* The customer has no available prepaid credit balance.
195+
*/
196+
NoCreditAvailable: "no_credit_available",
197+
}
198+
199+
/**
200+
* Query error within a partially successful governance query response.
201+
*/
202+
@friendlyName("GovernanceQueryError")
203+
model GovernanceQueryError is Shared.BaseError<GovernanceQueryErrorCode> {
204+
/**
205+
* The customer identifier from the request that produced this error.
206+
*/
207+
@visibility(Lifecycle.Read)
208+
@summary("Customer identifier")
209+
customer?: string;
210+
}
211+
212+
/**
213+
* Error code for a governance query failure.
214+
*/
215+
@friendlyName("GovernanceQueryErrorCode")
216+
enum GovernanceQueryErrorCode {
217+
/**
218+
* Default zero value. Reserved for forward compatibility.
219+
*/
220+
Unknown: "unknown",
221+
222+
/**
223+
* The provided identifier could not be resolved to any customer
224+
* (neither by customer `key` nor by a usage-attribution subject `key`).
225+
*/
226+
CustomerNotFound: "customer_not_found",
227+
}
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
@extension(Shared.PrivateExtension, true)
28+
@post
29+
@route("/query")
30+
@operationId("query-governance-access")
31+
@summary("Query governance access")
32+
query(
33+
...Common.CursorPaginationQuery,
34+
@body _: GovernanceQueryRequest,
35+
): GovernanceQueryResponse | Common.ErrorResponses;
36+
}

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

Lines changed: 9 additions & 0 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

@@ -188,6 +193,10 @@ interface PlanAddonEndpoints extends ProductCatalog.PlanAddonOperations {}
188193
interface OrganizationDefaultTaxCodesEndpoints
189194
extends Defaults.OrganizationDefaultTaxCodesOperations {}
190195

196+
@route("/openmeter/governance")
197+
@tag(Shared.GovernanceTag)
198+
interface GovernanceEndpoints extends Governance.GovernanceOperations {}
199+
191200
/**
192201
* The system account access token is meant for automations and integrations that
193202
* are not directly associated with a human identity.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import "./features/index.tsp";
1414
import "./llmcost/index.tsp";
1515
import "./productcatalog/index.tsp";
1616
import "./defaults/index.tsp";
17+
import "./governance/index.tsp";
1718

1819
using TypeSpec.Http;
1920
using TypeSpec.OpenAPI;
@@ -60,6 +61,10 @@ using TypeSpec.OpenAPI;
6061
#{ description: Shared.ProductCatalogDescription }
6162
)
6263
@tagMetadata(Shared.DefaultsTag, #{ description: Shared.DefaultsDescription })
64+
@tagMetadata(
65+
Shared.GovernanceTag,
66+
#{ description: Shared.GovernanceDescription }
67+
)
6368
namespace OpenMeter;
6469

6570
@route("/openmeter/events")
@@ -192,3 +197,7 @@ interface PlanAddonEndpoints extends ProductCatalog.PlanAddonOperations {}
192197
@tag(Shared.DefaultsTag)
193198
interface OrganizationDefaultTaxCodesEndpoints
194199
extends Defaults.OrganizationDefaultTaxCodesOperations {}
200+
201+
@route("/openmeter/governance")
202+
@tag(Shared.GovernanceTag)
203+
interface GovernanceEndpoints extends Governance.GovernanceOperations {}

0 commit comments

Comments
 (0)