codecraftai-restclient-pychestrate 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccai/pychestrate/py.typed +0 -0
- ccai/pychestrate/rest/client/__init__.py +0 -0
- ccai/pychestrate/rest/client/generation/__init__.py +0 -0
- ccai/pychestrate/rest/client/generation/pychestraterestclientgenerator.py +98 -0
- ccai/pychestrate/rest/client/generation/specs/pychestrate.yaml +3757 -0
- ccai/pychestrate/rest/client/groups/__init__.py +0 -0
- ccai/pychestrate/rest/client/groups/agents.py +171 -0
- ccai/pychestrate/rest/client/groups/artifacts.py +103 -0
- ccai/pychestrate/rest/client/groups/audit.py +99 -0
- ccai/pychestrate/rest/client/groups/auth.py +55 -0
- ccai/pychestrate/rest/client/groups/clients.py +287 -0
- ccai/pychestrate/rest/client/groups/dispatch.py +118 -0
- ccai/pychestrate/rest/client/groups/enrollment.py +212 -0
- ccai/pychestrate/rest/client/groups/flags.py +332 -0
- ccai/pychestrate/rest/client/groups/me.py +55 -0
- ccai/pychestrate/rest/client/groups/notifications.py +128 -0
- ccai/pychestrate/rest/client/groups/pluginapi.py +128 -0
- ccai/pychestrate/rest/client/groups/pods.py +558 -0
- ccai/pychestrate/rest/client/groups/progress.py +64 -0
- ccai/pychestrate/rest/client/groups/projects.py +420 -0
- ccai/pychestrate/rest/client/groups/projecttemplates.py +91 -0
- ccai/pychestrate/rest/client/groups/rbac.py +243 -0
- ccai/pychestrate/rest/client/groups/results.py +193 -0
- ccai/pychestrate/rest/client/groups/retention.py +169 -0
- ccai/pychestrate/rest/client/groups/runs.py +152 -0
- ccai/pychestrate/rest/client/groups/secrets.py +91 -0
- ccai/pychestrate/rest/client/groups/tagging.py +216 -0
- ccai/pychestrate/rest/client/groups/testcatalog.py +152 -0
- ccai/pychestrate/rest/client/groups/testruns.py +58 -0
- ccai/pychestrate/rest/client/groups/users.py +91 -0
- ccai/pychestrate/rest/client/groups/variables.py +228 -0
- ccai/pychestrate/rest/client/models/__init__.py +0 -0
- ccai/pychestrate/rest/client/models/agents.py +34 -0
- ccai/pychestrate/rest/client/models/artifactlocaluploadresult.py +18 -0
- ccai/pychestrate/rest/client/models/artifacts.py +24 -0
- ccai/pychestrate/rest/client/models/artifactuploadtarget.py +20 -0
- ccai/pychestrate/rest/client/models/audit.py +18 -0
- ccai/pychestrate/rest/client/models/auth.py +15 -0
- ccai/pychestrate/rest/client/models/capabilityentry.py +15 -0
- ccai/pychestrate/rest/client/models/client.py +19 -0
- ccai/pychestrate/rest/client/models/clients.py +40 -0
- ccai/pychestrate/rest/client/models/dispatch.py +26 -0
- ccai/pychestrate/rest/client/models/enrollment.py +39 -0
- ccai/pychestrate/rest/client/models/flags.py +55 -0
- ccai/pychestrate/rest/client/models/jobdeadline.py +11 -0
- ccai/pychestrate/rest/client/models/jobenvelope.py +38 -0
- ccai/pychestrate/rest/client/models/jobforwardinginfo.py +12 -0
- ccai/pychestrate/rest/client/models/jobpipelineref.py +11 -0
- ccai/pychestrate/rest/client/models/jobrefs.py +14 -0
- ccai/pychestrate/rest/client/models/jobresultslocator.py +11 -0
- ccai/pychestrate/rest/client/models/me.py +14 -0
- ccai/pychestrate/rest/client/models/notifications.py +24 -0
- ccai/pychestrate/rest/client/models/pagemeta.py +18 -0
- ccai/pychestrate/rest/client/models/pluginapi.py +21 -0
- ccai/pychestrate/rest/client/models/podmember.py +14 -0
- ccai/pychestrate/rest/client/models/pods.py +75 -0
- ccai/pychestrate/rest/client/models/podtopologylink.py +15 -0
- ccai/pychestrate/rest/client/models/problem.py +19 -0
- ccai/pychestrate/rest/client/models/progress.py +18 -0
- ccai/pychestrate/rest/client/models/progressrecord.py +26 -0
- ccai/pychestrate/rest/client/models/progresssummary.py +29 -0
- ccai/pychestrate/rest/client/models/projects.py +49 -0
- ccai/pychestrate/rest/client/models/projecttemplates.py +14 -0
- ccai/pychestrate/rest/client/models/rbac.py +46 -0
- ccai/pychestrate/rest/client/models/results.py +56 -0
- ccai/pychestrate/rest/client/models/retention.py +26 -0
- ccai/pychestrate/rest/client/models/runevent.py +25 -0
- ccai/pychestrate/rest/client/models/runs.py +32 -0
- ccai/pychestrate/rest/client/models/scopedsecret.py +13 -0
- ccai/pychestrate/rest/client/models/secrets.py +16 -0
- ccai/pychestrate/rest/client/models/signingkey.py +19 -0
- ccai/pychestrate/rest/client/models/tagging.py +24 -0
- ccai/pychestrate/rest/client/models/testcatalog.py +50 -0
- ccai/pychestrate/rest/client/models/testcatalogimportsummary.py +16 -0
- ccai/pychestrate/rest/client/models/testruns.py +25 -0
- ccai/pychestrate/rest/client/models/users.py +23 -0
- ccai/pychestrate/rest/client/models/variableeffectiveentry.py +13 -0
- ccai/pychestrate/rest/client/models/variables.py +30 -0
- ccai/pychestrate/rest/client/pychestraterestclient.py +166 -0
- ccai/pychestrate/rest/client/pychestraterestclientbase.py +147 -0
- codecraftai_restclient_pychestrate-1.2.0.dist-info/METADATA +96 -0
- codecraftai_restclient_pychestrate-1.2.0.dist-info/RECORD +84 -0
- codecraftai_restclient_pychestrate-1.2.0.dist-info/WHEEL +4 -0
- codecraftai_restclient_pychestrate-1.2.0.dist-info/licenses/LICENSE.md +204 -0
|
@@ -0,0 +1,3757 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: Pychestrate Service API
|
|
4
|
+
version: 0.0.7-preview
|
|
5
|
+
description: |
|
|
6
|
+
Authoritative REST contract for the **Pychestrate** continuous integration
|
|
7
|
+
and orchestration service.
|
|
8
|
+
|
|
9
|
+
This document is the single source of truth for the `/api/v1/*` HTTP
|
|
10
|
+
surface. It is consumed by:
|
|
11
|
+
|
|
12
|
+
* `codecraftai-restclient-base`'s `RestClientGenerator`, which produces the
|
|
13
|
+
typed Python client `codecraftai-restclient-pychestrate`.
|
|
14
|
+
* Server-side route implementations in the `pychestrate-service` repo
|
|
15
|
+
(route handlers are expected to conform to the schemas declared here).
|
|
16
|
+
* External integrators and tooling authors.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
* Every endpoint currently implemented under
|
|
21
|
+
`source/site/ccai/pychestrate/webservice/**/restapi/v1/*_routes.py`
|
|
22
|
+
is described.
|
|
23
|
+
* Endpoints planned for the Phase 3 minimum-viable test-run slice
|
|
24
|
+
(runs, dispatch ack, progress, results, test catalog, artifacts,
|
|
25
|
+
SSE events) are also described — these are flagged with the
|
|
26
|
+
`x-pychestrate-status: planned` extension on the operation.
|
|
27
|
+
|
|
28
|
+
The HTML admin / consumer UI surfaces under `/admin/*`, `/setup/*`,
|
|
29
|
+
`/login`, and the projects / resources blueprints are **not** part of
|
|
30
|
+
this contract; they are server-rendered HTML.
|
|
31
|
+
|
|
32
|
+
## Conventions
|
|
33
|
+
|
|
34
|
+
* All paths are versioned under `/api/v1/`.
|
|
35
|
+
* Path / query / request / response field names are `snake_case`.
|
|
36
|
+
* Resource identifiers in paths use snake_case (`run_id`,
|
|
37
|
+
`project_id`, `pod_id`, ...).
|
|
38
|
+
* Non-CRUD verbs on a resource use the colon-action form
|
|
39
|
+
(`/projects/{project_id}/test-catalog:import`).
|
|
40
|
+
* All timestamps are ISO-8601 / RFC 3339 UTC strings
|
|
41
|
+
(`2026-05-17T19:42:00Z`).
|
|
42
|
+
* Every 4xx / 5xx response is a RFC 7807 Problem Document
|
|
43
|
+
(`application/problem+json`) — see the `Problem` schema.
|
|
44
|
+
* Paginated list endpoints return a `Page<T>` envelope (see the
|
|
45
|
+
`Page` schema for the shape) so the codegen surfaces them through
|
|
46
|
+
`ccai.restclient.base.pagedlist.PagedList`.
|
|
47
|
+
contact:
|
|
48
|
+
name: Code Craft AI LLC
|
|
49
|
+
url: https://github.com/automationmojo/pychestrate-service
|
|
50
|
+
license:
|
|
51
|
+
name: Apache-2.0
|
|
52
|
+
url: https://www.apache.org/licenses/LICENSE-2.0.html
|
|
53
|
+
|
|
54
|
+
servers:
|
|
55
|
+
- url: https://tc-app-server.tailb75ce6.ts.net/api/v1
|
|
56
|
+
description: Author's live alpha deployment (Tailscale-fronted).
|
|
57
|
+
- url: https://{host}/api/v1
|
|
58
|
+
description: Generic templated server; supply the deployment host.
|
|
59
|
+
variables:
|
|
60
|
+
host:
|
|
61
|
+
default: pychestrate.example.com
|
|
62
|
+
description: Hostname of the Pychestrate service.
|
|
63
|
+
|
|
64
|
+
tags:
|
|
65
|
+
- name: auth
|
|
66
|
+
description: Local-credential authentication and session lifecycle.
|
|
67
|
+
- name: me
|
|
68
|
+
description: Permissions and identity of the currently-authenticated actor.
|
|
69
|
+
- name: users
|
|
70
|
+
description: User account administration.
|
|
71
|
+
- name: rbac
|
|
72
|
+
description: Roles, permissions, role bindings, and permission checks.
|
|
73
|
+
- name: audit
|
|
74
|
+
description: Append-only audit log access.
|
|
75
|
+
- name: secrets
|
|
76
|
+
description: Encrypted-secrets read API (writes are out-of-band today).
|
|
77
|
+
- name: variables
|
|
78
|
+
description: Hierarchical typed configuration variables.
|
|
79
|
+
- name: flags
|
|
80
|
+
description: Feature flags (runtime toggles).
|
|
81
|
+
- name: tagging
|
|
82
|
+
description: Shared tag model across entities.
|
|
83
|
+
- name: retention
|
|
84
|
+
description: Per-data-class retention policies.
|
|
85
|
+
- name: notifications
|
|
86
|
+
description: Notification channels (email, Slack, webhook, ...).
|
|
87
|
+
- name: project_templates
|
|
88
|
+
description: Scaffolded new-project presets.
|
|
89
|
+
- name: projects
|
|
90
|
+
description: Project workspaces — the top-level entity that owns runs,
|
|
91
|
+
pods, members, repositories, builds, and tests.
|
|
92
|
+
- name: clients
|
|
93
|
+
description: Registered pod client agents.
|
|
94
|
+
- name: enrollment
|
|
95
|
+
description: Client-enrollment bootstrap tokens (mTLS issuance).
|
|
96
|
+
- name: agents
|
|
97
|
+
description: Agent rollouts and per-client capability inventory.
|
|
98
|
+
- name: pods
|
|
99
|
+
description: Pod definitions, registrations, topology, and templates.
|
|
100
|
+
- name: plugin_api
|
|
101
|
+
description: Plugin-API catalog metadata for installed extensions.
|
|
102
|
+
- name: runs
|
|
103
|
+
description: Run-state reads (`GET /runs/...`).
|
|
104
|
+
- name: test_runs
|
|
105
|
+
description: Trigger a new test run on a project + pod.
|
|
106
|
+
- name: test_catalog
|
|
107
|
+
description: Per-project test-catalog entries.
|
|
108
|
+
- name: dispatch
|
|
109
|
+
description: Pod-client → server dispatch acknowledgements.
|
|
110
|
+
- name: progress
|
|
111
|
+
description: Pod-client → server live progress ingestion.
|
|
112
|
+
- name: results
|
|
113
|
+
description: Pod-client → server final-result manifests.
|
|
114
|
+
- name: artifacts
|
|
115
|
+
description: Artifact listings and browsing for a run.
|
|
116
|
+
- name: metrics
|
|
117
|
+
description: UI-facing metrics endpoints (planned).
|
|
118
|
+
|
|
119
|
+
security:
|
|
120
|
+
- pychestrate_session: []
|
|
121
|
+
- pychestrate_api_key: []
|
|
122
|
+
|
|
123
|
+
paths:
|
|
124
|
+
|
|
125
|
+
# =====================================================================
|
|
126
|
+
# auth
|
|
127
|
+
# =====================================================================
|
|
128
|
+
|
|
129
|
+
/auth/logout:
|
|
130
|
+
post:
|
|
131
|
+
tags: [auth]
|
|
132
|
+
summary: Close the current session.
|
|
133
|
+
operationId: auth_logout
|
|
134
|
+
description: |
|
|
135
|
+
Close the session named in the request body. Returns
|
|
136
|
+
`{session_id, status: "closed"}` on success.
|
|
137
|
+
requestBody:
|
|
138
|
+
required: true
|
|
139
|
+
content:
|
|
140
|
+
application/json:
|
|
141
|
+
schema:
|
|
142
|
+
$ref: '#/components/schemas/LogoutRequest'
|
|
143
|
+
responses:
|
|
144
|
+
'200':
|
|
145
|
+
description: Session closed.
|
|
146
|
+
content:
|
|
147
|
+
application/json:
|
|
148
|
+
schema:
|
|
149
|
+
$ref: '#/components/schemas/LogoutResponse'
|
|
150
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
151
|
+
'503': { $ref: '#/components/responses/ServiceUnavailable' }
|
|
152
|
+
|
|
153
|
+
# =====================================================================
|
|
154
|
+
# me
|
|
155
|
+
# =====================================================================
|
|
156
|
+
|
|
157
|
+
/me/permissions:
|
|
158
|
+
get:
|
|
159
|
+
tags: [me]
|
|
160
|
+
summary: List the current actor's effective permissions.
|
|
161
|
+
operationId: me_get_permissions
|
|
162
|
+
description: |
|
|
163
|
+
Returns the set of permission keys granted to the calling actor by
|
|
164
|
+
the RBAC evaluator, plus the actor's user_id and a list of role
|
|
165
|
+
bindings.
|
|
166
|
+
responses:
|
|
167
|
+
'200':
|
|
168
|
+
description: Effective permission snapshot.
|
|
169
|
+
content:
|
|
170
|
+
application/json:
|
|
171
|
+
schema:
|
|
172
|
+
$ref: '#/components/schemas/MePermissions'
|
|
173
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
174
|
+
|
|
175
|
+
# =====================================================================
|
|
176
|
+
# users
|
|
177
|
+
# =====================================================================
|
|
178
|
+
|
|
179
|
+
/identity/users:
|
|
180
|
+
post:
|
|
181
|
+
tags: [users]
|
|
182
|
+
summary: Create a user account.
|
|
183
|
+
operationId: users_create
|
|
184
|
+
requestBody:
|
|
185
|
+
required: true
|
|
186
|
+
content:
|
|
187
|
+
application/json:
|
|
188
|
+
schema:
|
|
189
|
+
$ref: '#/components/schemas/UserCreateRequest'
|
|
190
|
+
responses:
|
|
191
|
+
'201':
|
|
192
|
+
description: User created.
|
|
193
|
+
content:
|
|
194
|
+
application/json:
|
|
195
|
+
schema:
|
|
196
|
+
$ref: '#/components/schemas/User'
|
|
197
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
198
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
199
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
200
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
201
|
+
|
|
202
|
+
/identity/users/{user_id}:
|
|
203
|
+
parameters:
|
|
204
|
+
- $ref: '#/components/parameters/UserIdParam'
|
|
205
|
+
get:
|
|
206
|
+
tags: [users]
|
|
207
|
+
summary: Read a user account.
|
|
208
|
+
operationId: users_get_by_id
|
|
209
|
+
responses:
|
|
210
|
+
'200':
|
|
211
|
+
description: User account.
|
|
212
|
+
content:
|
|
213
|
+
application/json:
|
|
214
|
+
schema:
|
|
215
|
+
$ref: '#/components/schemas/User'
|
|
216
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
217
|
+
|
|
218
|
+
# =====================================================================
|
|
219
|
+
# rbac
|
|
220
|
+
# =====================================================================
|
|
221
|
+
|
|
222
|
+
/rbac/permissions:
|
|
223
|
+
get:
|
|
224
|
+
tags: [rbac]
|
|
225
|
+
summary: List declared RBAC permission keys.
|
|
226
|
+
operationId: rbac_list_permissions
|
|
227
|
+
responses:
|
|
228
|
+
'200':
|
|
229
|
+
description: All declared permissions.
|
|
230
|
+
content:
|
|
231
|
+
application/json:
|
|
232
|
+
schema:
|
|
233
|
+
type: object
|
|
234
|
+
required: [items]
|
|
235
|
+
properties:
|
|
236
|
+
items:
|
|
237
|
+
type: array
|
|
238
|
+
items: { $ref: '#/components/schemas/RbacPermission' }
|
|
239
|
+
/rbac/roles:
|
|
240
|
+
get:
|
|
241
|
+
tags: [rbac]
|
|
242
|
+
summary: List declared roles.
|
|
243
|
+
operationId: rbac_list_roles
|
|
244
|
+
responses:
|
|
245
|
+
'200':
|
|
246
|
+
description: All declared roles.
|
|
247
|
+
content:
|
|
248
|
+
application/json:
|
|
249
|
+
schema:
|
|
250
|
+
type: object
|
|
251
|
+
required: [items]
|
|
252
|
+
properties:
|
|
253
|
+
items:
|
|
254
|
+
type: array
|
|
255
|
+
items: { $ref: '#/components/schemas/RbacRole' }
|
|
256
|
+
/rbac/bindings:
|
|
257
|
+
get:
|
|
258
|
+
tags: [rbac]
|
|
259
|
+
summary: List role bindings.
|
|
260
|
+
operationId: rbac_list_bindings
|
|
261
|
+
parameters:
|
|
262
|
+
- in: query
|
|
263
|
+
name: user_id
|
|
264
|
+
schema: { type: string, format: uuid }
|
|
265
|
+
- in: query
|
|
266
|
+
name: project_id
|
|
267
|
+
schema: { type: string, format: uuid }
|
|
268
|
+
responses:
|
|
269
|
+
'200':
|
|
270
|
+
description: Matching role bindings.
|
|
271
|
+
content:
|
|
272
|
+
application/json:
|
|
273
|
+
schema:
|
|
274
|
+
type: object
|
|
275
|
+
required: [items]
|
|
276
|
+
properties:
|
|
277
|
+
items:
|
|
278
|
+
type: array
|
|
279
|
+
items: { $ref: '#/components/schemas/RbacRoleBinding' }
|
|
280
|
+
post:
|
|
281
|
+
tags: [rbac]
|
|
282
|
+
summary: Create a role binding.
|
|
283
|
+
operationId: rbac_create_binding
|
|
284
|
+
requestBody:
|
|
285
|
+
required: true
|
|
286
|
+
content:
|
|
287
|
+
application/json:
|
|
288
|
+
schema:
|
|
289
|
+
$ref: '#/components/schemas/RbacRoleBindingCreateRequest'
|
|
290
|
+
responses:
|
|
291
|
+
'201':
|
|
292
|
+
description: Role binding created.
|
|
293
|
+
content:
|
|
294
|
+
application/json:
|
|
295
|
+
schema: { $ref: '#/components/schemas/RbacRoleBinding' }
|
|
296
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
297
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
298
|
+
/rbac/bindings/{binding_id}:
|
|
299
|
+
parameters:
|
|
300
|
+
- in: path
|
|
301
|
+
name: binding_id
|
|
302
|
+
required: true
|
|
303
|
+
schema: { type: string, format: uuid }
|
|
304
|
+
delete:
|
|
305
|
+
tags: [rbac]
|
|
306
|
+
summary: Delete a role binding.
|
|
307
|
+
operationId: rbac_delete_binding
|
|
308
|
+
responses:
|
|
309
|
+
'204':
|
|
310
|
+
description: Binding deleted.
|
|
311
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
312
|
+
/rbac/check:
|
|
313
|
+
post:
|
|
314
|
+
tags: [rbac]
|
|
315
|
+
summary: Check whether the actor has a permission in a scope.
|
|
316
|
+
operationId: rbac_check_permission
|
|
317
|
+
requestBody:
|
|
318
|
+
required: true
|
|
319
|
+
content:
|
|
320
|
+
application/json:
|
|
321
|
+
schema:
|
|
322
|
+
$ref: '#/components/schemas/RbacCheckRequest'
|
|
323
|
+
responses:
|
|
324
|
+
'200':
|
|
325
|
+
description: Permission decision.
|
|
326
|
+
content:
|
|
327
|
+
application/json:
|
|
328
|
+
schema: { $ref: '#/components/schemas/RbacCheckResponse' }
|
|
329
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
330
|
+
|
|
331
|
+
# =====================================================================
|
|
332
|
+
# audit
|
|
333
|
+
# =====================================================================
|
|
334
|
+
|
|
335
|
+
/audit:
|
|
336
|
+
get:
|
|
337
|
+
tags: [audit]
|
|
338
|
+
summary: List audit-log records.
|
|
339
|
+
operationId: audit_list
|
|
340
|
+
parameters:
|
|
341
|
+
- in: query
|
|
342
|
+
name: limit
|
|
343
|
+
schema: { type: integer, minimum: 1, maximum: 1000, default: 100 }
|
|
344
|
+
- in: query
|
|
345
|
+
name: after_sequence
|
|
346
|
+
description: Only return records whose sequence_number is greater than this value.
|
|
347
|
+
schema: { type: integer, minimum: 0 }
|
|
348
|
+
- in: query
|
|
349
|
+
name: actor_id
|
|
350
|
+
schema: { type: string, format: uuid }
|
|
351
|
+
- in: query
|
|
352
|
+
name: action
|
|
353
|
+
schema: { type: string }
|
|
354
|
+
responses:
|
|
355
|
+
'200':
|
|
356
|
+
description: Audit records (ordered by sequence_number ascending).
|
|
357
|
+
content:
|
|
358
|
+
application/json:
|
|
359
|
+
schema:
|
|
360
|
+
type: object
|
|
361
|
+
required: [items]
|
|
362
|
+
properties:
|
|
363
|
+
items:
|
|
364
|
+
type: array
|
|
365
|
+
items: { $ref: '#/components/schemas/AuditRecord' }
|
|
366
|
+
/audit/{sequence_number}:
|
|
367
|
+
parameters:
|
|
368
|
+
- in: path
|
|
369
|
+
name: sequence_number
|
|
370
|
+
required: true
|
|
371
|
+
schema: { type: integer, minimum: 0 }
|
|
372
|
+
get:
|
|
373
|
+
tags: [audit]
|
|
374
|
+
summary: Read a single audit record by sequence number.
|
|
375
|
+
operationId: audit_get_by_sequence
|
|
376
|
+
responses:
|
|
377
|
+
'200':
|
|
378
|
+
description: Audit record.
|
|
379
|
+
content:
|
|
380
|
+
application/json:
|
|
381
|
+
schema: { $ref: '#/components/schemas/AuditRecord' }
|
|
382
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
383
|
+
|
|
384
|
+
# =====================================================================
|
|
385
|
+
# secrets
|
|
386
|
+
# =====================================================================
|
|
387
|
+
|
|
388
|
+
/secrets:
|
|
389
|
+
get:
|
|
390
|
+
tags: [secrets]
|
|
391
|
+
summary: List secret references (metadata only — no plaintext).
|
|
392
|
+
operationId: secrets_list
|
|
393
|
+
responses:
|
|
394
|
+
'200':
|
|
395
|
+
description: Secret references.
|
|
396
|
+
content:
|
|
397
|
+
application/json:
|
|
398
|
+
schema:
|
|
399
|
+
type: object
|
|
400
|
+
required: [items]
|
|
401
|
+
properties:
|
|
402
|
+
items:
|
|
403
|
+
type: array
|
|
404
|
+
items: { $ref: '#/components/schemas/SecretRef' }
|
|
405
|
+
/secrets/{secret_ref}:
|
|
406
|
+
parameters:
|
|
407
|
+
- in: path
|
|
408
|
+
name: secret_ref
|
|
409
|
+
description: Slash-delimited secret reference path (e.g. `vault/myteam/api-token`).
|
|
410
|
+
required: true
|
|
411
|
+
schema: { type: string }
|
|
412
|
+
get:
|
|
413
|
+
tags: [secrets]
|
|
414
|
+
summary: Read a single secret reference (metadata only).
|
|
415
|
+
operationId: secrets_get_by_ref
|
|
416
|
+
responses:
|
|
417
|
+
'200':
|
|
418
|
+
description: Secret reference.
|
|
419
|
+
content:
|
|
420
|
+
application/json:
|
|
421
|
+
schema: { $ref: '#/components/schemas/SecretRef' }
|
|
422
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
423
|
+
|
|
424
|
+
# =====================================================================
|
|
425
|
+
# variables
|
|
426
|
+
# =====================================================================
|
|
427
|
+
|
|
428
|
+
/variables/effective:
|
|
429
|
+
get:
|
|
430
|
+
tags: [variables]
|
|
431
|
+
summary: Resolve effective variable values for a project / pod / run scope.
|
|
432
|
+
operationId: variables_get_effective
|
|
433
|
+
parameters:
|
|
434
|
+
- in: query
|
|
435
|
+
name: project_id
|
|
436
|
+
schema: { type: string, format: uuid }
|
|
437
|
+
- in: query
|
|
438
|
+
name: pod_id
|
|
439
|
+
schema: { type: string, format: uuid }
|
|
440
|
+
- in: query
|
|
441
|
+
name: run_id
|
|
442
|
+
schema: { type: string, format: uuid }
|
|
443
|
+
responses:
|
|
444
|
+
'200':
|
|
445
|
+
description: Effective variable map with inheritance trace.
|
|
446
|
+
content:
|
|
447
|
+
application/json:
|
|
448
|
+
schema: { $ref: '#/components/schemas/VariableEffectiveSet' }
|
|
449
|
+
|
|
450
|
+
/variables/{scope}/{scope_id}:
|
|
451
|
+
parameters:
|
|
452
|
+
- $ref: '#/components/parameters/VariableScopeParam'
|
|
453
|
+
- $ref: '#/components/parameters/VariableScopeIdParam'
|
|
454
|
+
get:
|
|
455
|
+
tags: [variables]
|
|
456
|
+
summary: List variables declared at a specific scope.
|
|
457
|
+
operationId: variables_list_scope
|
|
458
|
+
responses:
|
|
459
|
+
'200':
|
|
460
|
+
description: Variables declared at the scope.
|
|
461
|
+
content:
|
|
462
|
+
application/json:
|
|
463
|
+
schema:
|
|
464
|
+
type: object
|
|
465
|
+
required: [items]
|
|
466
|
+
properties:
|
|
467
|
+
items:
|
|
468
|
+
type: array
|
|
469
|
+
items: { $ref: '#/components/schemas/Variable' }
|
|
470
|
+
|
|
471
|
+
/variables/{scope}/{scope_id}/{name}:
|
|
472
|
+
parameters:
|
|
473
|
+
- $ref: '#/components/parameters/VariableScopeParam'
|
|
474
|
+
- $ref: '#/components/parameters/VariableScopeIdParam'
|
|
475
|
+
- in: path
|
|
476
|
+
name: name
|
|
477
|
+
required: true
|
|
478
|
+
schema: { type: string }
|
|
479
|
+
get:
|
|
480
|
+
tags: [variables]
|
|
481
|
+
summary: Read one variable at a specific scope.
|
|
482
|
+
operationId: variables_get_one
|
|
483
|
+
responses:
|
|
484
|
+
'200':
|
|
485
|
+
description: Variable definition at the scope.
|
|
486
|
+
content:
|
|
487
|
+
application/json:
|
|
488
|
+
schema: { $ref: '#/components/schemas/Variable' }
|
|
489
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
490
|
+
put:
|
|
491
|
+
tags: [variables]
|
|
492
|
+
summary: Upsert one variable at a specific scope.
|
|
493
|
+
operationId: variables_put_one
|
|
494
|
+
requestBody:
|
|
495
|
+
required: true
|
|
496
|
+
content:
|
|
497
|
+
application/json:
|
|
498
|
+
schema: { $ref: '#/components/schemas/VariableUpsertRequest' }
|
|
499
|
+
responses:
|
|
500
|
+
'200':
|
|
501
|
+
description: Variable upserted.
|
|
502
|
+
content:
|
|
503
|
+
application/json:
|
|
504
|
+
schema: { $ref: '#/components/schemas/Variable' }
|
|
505
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
506
|
+
delete:
|
|
507
|
+
tags: [variables]
|
|
508
|
+
summary: Delete one variable at a specific scope.
|
|
509
|
+
operationId: variables_delete_one
|
|
510
|
+
responses:
|
|
511
|
+
'204':
|
|
512
|
+
description: Variable deleted.
|
|
513
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
514
|
+
|
|
515
|
+
# =====================================================================
|
|
516
|
+
# flags
|
|
517
|
+
# =====================================================================
|
|
518
|
+
|
|
519
|
+
/flags:
|
|
520
|
+
get:
|
|
521
|
+
tags: [flags]
|
|
522
|
+
summary: List feature flags.
|
|
523
|
+
operationId: flags_list
|
|
524
|
+
responses:
|
|
525
|
+
'200':
|
|
526
|
+
description: Declared flags.
|
|
527
|
+
content:
|
|
528
|
+
application/json:
|
|
529
|
+
schema:
|
|
530
|
+
type: object
|
|
531
|
+
required: [items]
|
|
532
|
+
properties:
|
|
533
|
+
items:
|
|
534
|
+
type: array
|
|
535
|
+
items: { $ref: '#/components/schemas/FeatureFlag' }
|
|
536
|
+
post:
|
|
537
|
+
tags: [flags]
|
|
538
|
+
summary: Create a new feature flag.
|
|
539
|
+
operationId: flags_create
|
|
540
|
+
requestBody:
|
|
541
|
+
required: true
|
|
542
|
+
content:
|
|
543
|
+
application/json:
|
|
544
|
+
schema: { $ref: '#/components/schemas/FeatureFlagCreateRequest' }
|
|
545
|
+
responses:
|
|
546
|
+
'201':
|
|
547
|
+
description: Flag created.
|
|
548
|
+
content:
|
|
549
|
+
application/json:
|
|
550
|
+
schema: { $ref: '#/components/schemas/FeatureFlag' }
|
|
551
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
552
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
553
|
+
/flags/backends:
|
|
554
|
+
get:
|
|
555
|
+
tags: [flags]
|
|
556
|
+
summary: List installed feature-flag backends.
|
|
557
|
+
operationId: flags_list_backends
|
|
558
|
+
responses:
|
|
559
|
+
'200':
|
|
560
|
+
description: Registered backends.
|
|
561
|
+
content:
|
|
562
|
+
application/json:
|
|
563
|
+
schema:
|
|
564
|
+
type: object
|
|
565
|
+
required: [items]
|
|
566
|
+
properties:
|
|
567
|
+
items:
|
|
568
|
+
type: array
|
|
569
|
+
items: { $ref: '#/components/schemas/FeatureFlagBackend' }
|
|
570
|
+
/flags/{flag_key}:
|
|
571
|
+
parameters:
|
|
572
|
+
- $ref: '#/components/parameters/FlagKeyParam'
|
|
573
|
+
get:
|
|
574
|
+
tags: [flags]
|
|
575
|
+
summary: Read a feature flag.
|
|
576
|
+
operationId: flags_get_by_key
|
|
577
|
+
responses:
|
|
578
|
+
'200':
|
|
579
|
+
description: Flag.
|
|
580
|
+
content:
|
|
581
|
+
application/json:
|
|
582
|
+
schema: { $ref: '#/components/schemas/FeatureFlag' }
|
|
583
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
584
|
+
patch:
|
|
585
|
+
tags: [flags]
|
|
586
|
+
summary: Update a feature flag.
|
|
587
|
+
operationId: flags_update
|
|
588
|
+
requestBody:
|
|
589
|
+
required: true
|
|
590
|
+
content:
|
|
591
|
+
application/json:
|
|
592
|
+
schema: { $ref: '#/components/schemas/FeatureFlagUpdateRequest' }
|
|
593
|
+
responses:
|
|
594
|
+
'200':
|
|
595
|
+
description: Flag updated.
|
|
596
|
+
content:
|
|
597
|
+
application/json:
|
|
598
|
+
schema: { $ref: '#/components/schemas/FeatureFlag' }
|
|
599
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
600
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
601
|
+
/flags/{flag_key}/retire:
|
|
602
|
+
parameters:
|
|
603
|
+
- $ref: '#/components/parameters/FlagKeyParam'
|
|
604
|
+
post:
|
|
605
|
+
tags: [flags]
|
|
606
|
+
summary: Retire a feature flag.
|
|
607
|
+
operationId: flags_retire
|
|
608
|
+
responses:
|
|
609
|
+
'200':
|
|
610
|
+
description: Flag retired.
|
|
611
|
+
content:
|
|
612
|
+
application/json:
|
|
613
|
+
schema: { $ref: '#/components/schemas/FeatureFlag' }
|
|
614
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
615
|
+
/flags/{flag_key}/history:
|
|
616
|
+
parameters:
|
|
617
|
+
- $ref: '#/components/parameters/FlagKeyParam'
|
|
618
|
+
get:
|
|
619
|
+
tags: [flags]
|
|
620
|
+
summary: Read change history for a feature flag.
|
|
621
|
+
operationId: flags_get_history
|
|
622
|
+
responses:
|
|
623
|
+
'200':
|
|
624
|
+
description: History entries.
|
|
625
|
+
content:
|
|
626
|
+
application/json:
|
|
627
|
+
schema:
|
|
628
|
+
type: object
|
|
629
|
+
required: [items]
|
|
630
|
+
properties:
|
|
631
|
+
items:
|
|
632
|
+
type: array
|
|
633
|
+
items: { $ref: '#/components/schemas/FeatureFlagHistoryEntry' }
|
|
634
|
+
/flags/{flag_key}/evaluate:
|
|
635
|
+
parameters:
|
|
636
|
+
- $ref: '#/components/parameters/FlagKeyParam'
|
|
637
|
+
post:
|
|
638
|
+
tags: [flags]
|
|
639
|
+
summary: Evaluate a feature flag against an actor / context.
|
|
640
|
+
operationId: flags_evaluate
|
|
641
|
+
requestBody:
|
|
642
|
+
required: true
|
|
643
|
+
content:
|
|
644
|
+
application/json:
|
|
645
|
+
schema: { $ref: '#/components/schemas/FeatureFlagEvaluateRequest' }
|
|
646
|
+
responses:
|
|
647
|
+
'200':
|
|
648
|
+
description: Evaluation result.
|
|
649
|
+
content:
|
|
650
|
+
application/json:
|
|
651
|
+
schema: { $ref: '#/components/schemas/FeatureFlagEvaluateResponse' }
|
|
652
|
+
|
|
653
|
+
# =====================================================================
|
|
654
|
+
# tagging
|
|
655
|
+
# =====================================================================
|
|
656
|
+
|
|
657
|
+
/tags/search:
|
|
658
|
+
get:
|
|
659
|
+
tags: [tagging]
|
|
660
|
+
summary: Search tag definitions.
|
|
661
|
+
operationId: tagging_search
|
|
662
|
+
parameters:
|
|
663
|
+
- in: query
|
|
664
|
+
name: q
|
|
665
|
+
schema: { type: string }
|
|
666
|
+
- in: query
|
|
667
|
+
name: taxonomy
|
|
668
|
+
schema: { type: string }
|
|
669
|
+
responses:
|
|
670
|
+
'200':
|
|
671
|
+
description: Matching tags.
|
|
672
|
+
content:
|
|
673
|
+
application/json:
|
|
674
|
+
schema:
|
|
675
|
+
type: object
|
|
676
|
+
required: [items]
|
|
677
|
+
properties:
|
|
678
|
+
items:
|
|
679
|
+
type: array
|
|
680
|
+
items: { $ref: '#/components/schemas/Tag' }
|
|
681
|
+
/tags/taxonomies:
|
|
682
|
+
get:
|
|
683
|
+
tags: [tagging]
|
|
684
|
+
summary: List declared tag taxonomies.
|
|
685
|
+
operationId: tagging_list_taxonomies
|
|
686
|
+
responses:
|
|
687
|
+
'200':
|
|
688
|
+
description: Taxonomies.
|
|
689
|
+
content:
|
|
690
|
+
application/json:
|
|
691
|
+
schema:
|
|
692
|
+
type: object
|
|
693
|
+
required: [items]
|
|
694
|
+
properties:
|
|
695
|
+
items:
|
|
696
|
+
type: array
|
|
697
|
+
items: { $ref: '#/components/schemas/TagTaxonomy' }
|
|
698
|
+
/tags/{entity_kind}/{entity_id}:
|
|
699
|
+
parameters:
|
|
700
|
+
- $ref: '#/components/parameters/EntityKindParam'
|
|
701
|
+
- $ref: '#/components/parameters/EntityIdParam'
|
|
702
|
+
get:
|
|
703
|
+
tags: [tagging]
|
|
704
|
+
summary: List tags attached to an entity.
|
|
705
|
+
operationId: tagging_list_for_entity
|
|
706
|
+
responses:
|
|
707
|
+
'200':
|
|
708
|
+
description: Tag attachments.
|
|
709
|
+
content:
|
|
710
|
+
application/json:
|
|
711
|
+
schema:
|
|
712
|
+
type: object
|
|
713
|
+
required: [items]
|
|
714
|
+
properties:
|
|
715
|
+
items:
|
|
716
|
+
type: array
|
|
717
|
+
items: { $ref: '#/components/schemas/Tag' }
|
|
718
|
+
post:
|
|
719
|
+
tags: [tagging]
|
|
720
|
+
summary: Attach a tag to an entity.
|
|
721
|
+
operationId: tagging_attach
|
|
722
|
+
requestBody:
|
|
723
|
+
required: true
|
|
724
|
+
content:
|
|
725
|
+
application/json:
|
|
726
|
+
schema: { $ref: '#/components/schemas/TagAttachRequest' }
|
|
727
|
+
responses:
|
|
728
|
+
'201':
|
|
729
|
+
description: Tag attached.
|
|
730
|
+
content:
|
|
731
|
+
application/json:
|
|
732
|
+
schema: { $ref: '#/components/schemas/Tag' }
|
|
733
|
+
/tags/{entity_kind}/{entity_id}/{tag}:
|
|
734
|
+
parameters:
|
|
735
|
+
- $ref: '#/components/parameters/EntityKindParam'
|
|
736
|
+
- $ref: '#/components/parameters/EntityIdParam'
|
|
737
|
+
- in: path
|
|
738
|
+
name: tag
|
|
739
|
+
required: true
|
|
740
|
+
schema: { type: string }
|
|
741
|
+
delete:
|
|
742
|
+
tags: [tagging]
|
|
743
|
+
summary: Detach a tag from an entity.
|
|
744
|
+
operationId: tagging_detach
|
|
745
|
+
responses:
|
|
746
|
+
'204':
|
|
747
|
+
description: Tag detached.
|
|
748
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
749
|
+
|
|
750
|
+
# =====================================================================
|
|
751
|
+
# retention
|
|
752
|
+
# =====================================================================
|
|
753
|
+
|
|
754
|
+
/retention/policies:
|
|
755
|
+
get:
|
|
756
|
+
tags: [retention]
|
|
757
|
+
summary: List retention policies.
|
|
758
|
+
operationId: retention_list
|
|
759
|
+
responses:
|
|
760
|
+
'200':
|
|
761
|
+
description: Retention policies.
|
|
762
|
+
content:
|
|
763
|
+
application/json:
|
|
764
|
+
schema:
|
|
765
|
+
type: object
|
|
766
|
+
required: [items]
|
|
767
|
+
properties:
|
|
768
|
+
items:
|
|
769
|
+
type: array
|
|
770
|
+
items: { $ref: '#/components/schemas/RetentionPolicy' }
|
|
771
|
+
post:
|
|
772
|
+
tags: [retention]
|
|
773
|
+
summary: Create a retention policy.
|
|
774
|
+
operationId: retention_create
|
|
775
|
+
requestBody:
|
|
776
|
+
required: true
|
|
777
|
+
content:
|
|
778
|
+
application/json:
|
|
779
|
+
schema: { $ref: '#/components/schemas/RetentionPolicyCreateRequest' }
|
|
780
|
+
responses:
|
|
781
|
+
'201':
|
|
782
|
+
description: Policy created.
|
|
783
|
+
content:
|
|
784
|
+
application/json:
|
|
785
|
+
schema: { $ref: '#/components/schemas/RetentionPolicy' }
|
|
786
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
787
|
+
/retention/policies/{policy_id}:
|
|
788
|
+
parameters:
|
|
789
|
+
- in: path
|
|
790
|
+
name: policy_id
|
|
791
|
+
required: true
|
|
792
|
+
schema: { type: string, format: uuid }
|
|
793
|
+
get:
|
|
794
|
+
tags: [retention]
|
|
795
|
+
summary: Read a retention policy.
|
|
796
|
+
operationId: retention_get_by_id
|
|
797
|
+
responses:
|
|
798
|
+
'200':
|
|
799
|
+
description: Policy.
|
|
800
|
+
content:
|
|
801
|
+
application/json:
|
|
802
|
+
schema: { $ref: '#/components/schemas/RetentionPolicy' }
|
|
803
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
804
|
+
/retention/policies/{policy_id}/publish:
|
|
805
|
+
parameters:
|
|
806
|
+
- in: path
|
|
807
|
+
name: policy_id
|
|
808
|
+
required: true
|
|
809
|
+
schema: { type: string, format: uuid }
|
|
810
|
+
post:
|
|
811
|
+
tags: [retention]
|
|
812
|
+
summary: Publish (activate) a retention policy.
|
|
813
|
+
operationId: retention_publish
|
|
814
|
+
responses:
|
|
815
|
+
'200':
|
|
816
|
+
description: Policy published.
|
|
817
|
+
content:
|
|
818
|
+
application/json:
|
|
819
|
+
schema: { $ref: '#/components/schemas/RetentionPolicy' }
|
|
820
|
+
|
|
821
|
+
# =====================================================================
|
|
822
|
+
# notifications
|
|
823
|
+
# =====================================================================
|
|
824
|
+
|
|
825
|
+
/notifications/channels:
|
|
826
|
+
get:
|
|
827
|
+
tags: [notifications]
|
|
828
|
+
summary: List notification channels.
|
|
829
|
+
operationId: notifications_list_channels
|
|
830
|
+
responses:
|
|
831
|
+
'200':
|
|
832
|
+
description: Channels.
|
|
833
|
+
content:
|
|
834
|
+
application/json:
|
|
835
|
+
schema:
|
|
836
|
+
type: object
|
|
837
|
+
required: [items]
|
|
838
|
+
properties:
|
|
839
|
+
items:
|
|
840
|
+
type: array
|
|
841
|
+
items: { $ref: '#/components/schemas/NotificationChannel' }
|
|
842
|
+
post:
|
|
843
|
+
tags: [notifications]
|
|
844
|
+
summary: Create a notification channel.
|
|
845
|
+
operationId: notifications_create_channel
|
|
846
|
+
requestBody:
|
|
847
|
+
required: true
|
|
848
|
+
content:
|
|
849
|
+
application/json:
|
|
850
|
+
schema: { $ref: '#/components/schemas/NotificationChannelCreateRequest' }
|
|
851
|
+
responses:
|
|
852
|
+
'201':
|
|
853
|
+
description: Channel created.
|
|
854
|
+
content:
|
|
855
|
+
application/json:
|
|
856
|
+
schema: { $ref: '#/components/schemas/NotificationChannel' }
|
|
857
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
858
|
+
/notifications/channels/{channel_id}:
|
|
859
|
+
parameters:
|
|
860
|
+
- in: path
|
|
861
|
+
name: channel_id
|
|
862
|
+
required: true
|
|
863
|
+
schema: { type: string, format: uuid }
|
|
864
|
+
get:
|
|
865
|
+
tags: [notifications]
|
|
866
|
+
summary: Read a notification channel.
|
|
867
|
+
operationId: notifications_get_channel
|
|
868
|
+
responses:
|
|
869
|
+
'200':
|
|
870
|
+
description: Channel.
|
|
871
|
+
content:
|
|
872
|
+
application/json:
|
|
873
|
+
schema: { $ref: '#/components/schemas/NotificationChannel' }
|
|
874
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
875
|
+
|
|
876
|
+
# =====================================================================
|
|
877
|
+
# project_templates
|
|
878
|
+
# =====================================================================
|
|
879
|
+
|
|
880
|
+
/project-templates:
|
|
881
|
+
get:
|
|
882
|
+
tags: [project_templates]
|
|
883
|
+
summary: List project templates.
|
|
884
|
+
operationId: project_templates_list
|
|
885
|
+
responses:
|
|
886
|
+
'200':
|
|
887
|
+
description: Templates.
|
|
888
|
+
content:
|
|
889
|
+
application/json:
|
|
890
|
+
schema:
|
|
891
|
+
type: object
|
|
892
|
+
required: [items]
|
|
893
|
+
properties:
|
|
894
|
+
items:
|
|
895
|
+
type: array
|
|
896
|
+
items: { $ref: '#/components/schemas/ProjectTemplate' }
|
|
897
|
+
/project-templates/{template_id}:
|
|
898
|
+
parameters:
|
|
899
|
+
- in: path
|
|
900
|
+
name: template_id
|
|
901
|
+
required: true
|
|
902
|
+
schema: { type: string }
|
|
903
|
+
get:
|
|
904
|
+
tags: [project_templates]
|
|
905
|
+
summary: Read a project template.
|
|
906
|
+
operationId: project_templates_get_by_id
|
|
907
|
+
responses:
|
|
908
|
+
'200':
|
|
909
|
+
description: Template.
|
|
910
|
+
content:
|
|
911
|
+
application/json:
|
|
912
|
+
schema: { $ref: '#/components/schemas/ProjectTemplate' }
|
|
913
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
914
|
+
|
|
915
|
+
# =====================================================================
|
|
916
|
+
# projects
|
|
917
|
+
# =====================================================================
|
|
918
|
+
|
|
919
|
+
/projects:
|
|
920
|
+
post:
|
|
921
|
+
tags: [projects]
|
|
922
|
+
summary: Create a project.
|
|
923
|
+
operationId: projects_create
|
|
924
|
+
requestBody:
|
|
925
|
+
required: true
|
|
926
|
+
content:
|
|
927
|
+
application/json:
|
|
928
|
+
schema: { $ref: '#/components/schemas/ProjectCreateRequest' }
|
|
929
|
+
responses:
|
|
930
|
+
'201':
|
|
931
|
+
description: Project created.
|
|
932
|
+
content:
|
|
933
|
+
application/json:
|
|
934
|
+
schema: { $ref: '#/components/schemas/Project' }
|
|
935
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
936
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
937
|
+
/projects/{slug}:
|
|
938
|
+
parameters:
|
|
939
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
940
|
+
get:
|
|
941
|
+
tags: [projects]
|
|
942
|
+
summary: Read a project by slug.
|
|
943
|
+
operationId: projects_get_by_slug
|
|
944
|
+
responses:
|
|
945
|
+
'200':
|
|
946
|
+
description: Project.
|
|
947
|
+
content:
|
|
948
|
+
application/json:
|
|
949
|
+
schema: { $ref: '#/components/schemas/Project' }
|
|
950
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
951
|
+
patch:
|
|
952
|
+
tags: [projects]
|
|
953
|
+
summary: Patch a project.
|
|
954
|
+
operationId: projects_patch
|
|
955
|
+
requestBody:
|
|
956
|
+
required: true
|
|
957
|
+
content:
|
|
958
|
+
application/json:
|
|
959
|
+
schema: { $ref: '#/components/schemas/ProjectPatchRequest' }
|
|
960
|
+
responses:
|
|
961
|
+
'200':
|
|
962
|
+
description: Project patched.
|
|
963
|
+
content:
|
|
964
|
+
application/json:
|
|
965
|
+
schema: { $ref: '#/components/schemas/Project' }
|
|
966
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
967
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
968
|
+
delete:
|
|
969
|
+
tags: [projects]
|
|
970
|
+
summary: Delete a project.
|
|
971
|
+
operationId: projects_delete
|
|
972
|
+
responses:
|
|
973
|
+
'204':
|
|
974
|
+
description: Project deleted.
|
|
975
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
976
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
977
|
+
/projects/{slug}/archive:
|
|
978
|
+
parameters:
|
|
979
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
980
|
+
post:
|
|
981
|
+
tags: [projects]
|
|
982
|
+
summary: Archive a project.
|
|
983
|
+
operationId: projects_archive
|
|
984
|
+
responses:
|
|
985
|
+
'200':
|
|
986
|
+
description: Project archived.
|
|
987
|
+
content:
|
|
988
|
+
application/json:
|
|
989
|
+
schema: { $ref: '#/components/schemas/Project' }
|
|
990
|
+
/projects/{slug}/unarchive:
|
|
991
|
+
parameters:
|
|
992
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
993
|
+
post:
|
|
994
|
+
tags: [projects]
|
|
995
|
+
summary: Un-archive a project.
|
|
996
|
+
operationId: projects_unarchive
|
|
997
|
+
responses:
|
|
998
|
+
'200':
|
|
999
|
+
description: Project un-archived.
|
|
1000
|
+
content:
|
|
1001
|
+
application/json:
|
|
1002
|
+
schema: { $ref: '#/components/schemas/Project' }
|
|
1003
|
+
/projects/{slug}/members:
|
|
1004
|
+
parameters:
|
|
1005
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
1006
|
+
get:
|
|
1007
|
+
tags: [projects]
|
|
1008
|
+
summary: List project members.
|
|
1009
|
+
operationId: projects_list_members
|
|
1010
|
+
responses:
|
|
1011
|
+
'200':
|
|
1012
|
+
description: Members.
|
|
1013
|
+
content:
|
|
1014
|
+
application/json:
|
|
1015
|
+
schema:
|
|
1016
|
+
type: object
|
|
1017
|
+
required: [items]
|
|
1018
|
+
properties:
|
|
1019
|
+
items:
|
|
1020
|
+
type: array
|
|
1021
|
+
items: { $ref: '#/components/schemas/ProjectMember' }
|
|
1022
|
+
post:
|
|
1023
|
+
tags: [projects]
|
|
1024
|
+
summary: Add a member to a project.
|
|
1025
|
+
operationId: projects_add_member
|
|
1026
|
+
requestBody:
|
|
1027
|
+
required: true
|
|
1028
|
+
content:
|
|
1029
|
+
application/json:
|
|
1030
|
+
schema: { $ref: '#/components/schemas/ProjectMemberAddRequest' }
|
|
1031
|
+
responses:
|
|
1032
|
+
'201':
|
|
1033
|
+
description: Member added.
|
|
1034
|
+
content:
|
|
1035
|
+
application/json:
|
|
1036
|
+
schema: { $ref: '#/components/schemas/ProjectMember' }
|
|
1037
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1038
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
1039
|
+
/projects/{slug}/members/{user_id}:
|
|
1040
|
+
parameters:
|
|
1041
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
1042
|
+
- $ref: '#/components/parameters/UserIdParam'
|
|
1043
|
+
delete:
|
|
1044
|
+
tags: [projects]
|
|
1045
|
+
summary: Remove a member's owner binding from the project.
|
|
1046
|
+
operationId: projects_remove_member
|
|
1047
|
+
responses:
|
|
1048
|
+
'204':
|
|
1049
|
+
description: Owner binding removed.
|
|
1050
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1051
|
+
/projects/{slug}/members/{user_id}/{role_name}:
|
|
1052
|
+
parameters:
|
|
1053
|
+
- $ref: '#/components/parameters/ProjectSlugParam'
|
|
1054
|
+
- $ref: '#/components/parameters/UserIdParam'
|
|
1055
|
+
- in: path
|
|
1056
|
+
name: role_name
|
|
1057
|
+
required: true
|
|
1058
|
+
schema: { type: string }
|
|
1059
|
+
delete:
|
|
1060
|
+
tags: [projects]
|
|
1061
|
+
summary: Remove a specific role binding from a member of the project.
|
|
1062
|
+
operationId: projects_remove_member_role
|
|
1063
|
+
responses:
|
|
1064
|
+
'204':
|
|
1065
|
+
description: Role binding removed.
|
|
1066
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1067
|
+
|
|
1068
|
+
# =====================================================================
|
|
1069
|
+
# clients
|
|
1070
|
+
# =====================================================================
|
|
1071
|
+
|
|
1072
|
+
/clients:
|
|
1073
|
+
get:
|
|
1074
|
+
tags: [clients]
|
|
1075
|
+
summary: List enrolled clients.
|
|
1076
|
+
operationId: clients_list
|
|
1077
|
+
parameters:
|
|
1078
|
+
- $ref: '#/components/parameters/PageParam'
|
|
1079
|
+
- $ref: '#/components/parameters/PerPageParam'
|
|
1080
|
+
responses:
|
|
1081
|
+
'200':
|
|
1082
|
+
description: Clients.
|
|
1083
|
+
content:
|
|
1084
|
+
application/json:
|
|
1085
|
+
schema:
|
|
1086
|
+
type: object
|
|
1087
|
+
required: [items]
|
|
1088
|
+
properties:
|
|
1089
|
+
items:
|
|
1090
|
+
type: array
|
|
1091
|
+
items: { $ref: '#/components/schemas/Client' }
|
|
1092
|
+
/clients/{client_id}:
|
|
1093
|
+
parameters:
|
|
1094
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1095
|
+
get:
|
|
1096
|
+
tags: [clients]
|
|
1097
|
+
summary: Read a client.
|
|
1098
|
+
operationId: clients_get_by_id
|
|
1099
|
+
responses:
|
|
1100
|
+
'200':
|
|
1101
|
+
description: Client.
|
|
1102
|
+
content:
|
|
1103
|
+
application/json:
|
|
1104
|
+
schema: { $ref: '#/components/schemas/Client' }
|
|
1105
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1106
|
+
/clients/{client_id}/revoke:
|
|
1107
|
+
parameters:
|
|
1108
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1109
|
+
post:
|
|
1110
|
+
tags: [clients]
|
|
1111
|
+
summary: Revoke a client's enrollment.
|
|
1112
|
+
operationId: clients_revoke
|
|
1113
|
+
requestBody:
|
|
1114
|
+
required: false
|
|
1115
|
+
content:
|
|
1116
|
+
application/json:
|
|
1117
|
+
schema: { $ref: '#/components/schemas/ClientRevokeRequest' }
|
|
1118
|
+
responses:
|
|
1119
|
+
'200':
|
|
1120
|
+
description: Client revoked.
|
|
1121
|
+
content:
|
|
1122
|
+
application/json:
|
|
1123
|
+
schema: { $ref: '#/components/schemas/Client' }
|
|
1124
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1125
|
+
/clients/{client_id}/attributes:
|
|
1126
|
+
parameters:
|
|
1127
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1128
|
+
put:
|
|
1129
|
+
tags: [clients]
|
|
1130
|
+
summary: Replace a client's attributes.
|
|
1131
|
+
operationId: clients_update_attributes
|
|
1132
|
+
requestBody:
|
|
1133
|
+
required: true
|
|
1134
|
+
content:
|
|
1135
|
+
application/json:
|
|
1136
|
+
schema: { $ref: '#/components/schemas/ClientAttributesUpdateRequest' }
|
|
1137
|
+
responses:
|
|
1138
|
+
'200':
|
|
1139
|
+
description: Attributes updated.
|
|
1140
|
+
content:
|
|
1141
|
+
application/json:
|
|
1142
|
+
schema: { $ref: '#/components/schemas/Client' }
|
|
1143
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1144
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1145
|
+
|
|
1146
|
+
# =====================================================================
|
|
1147
|
+
# enrollment
|
|
1148
|
+
# =====================================================================
|
|
1149
|
+
|
|
1150
|
+
/enrollment-tokens:
|
|
1151
|
+
get:
|
|
1152
|
+
tags: [enrollment]
|
|
1153
|
+
summary: List enrollment tokens.
|
|
1154
|
+
operationId: enrollment_list_tokens
|
|
1155
|
+
responses:
|
|
1156
|
+
'200':
|
|
1157
|
+
description: Tokens.
|
|
1158
|
+
content:
|
|
1159
|
+
application/json:
|
|
1160
|
+
schema:
|
|
1161
|
+
type: object
|
|
1162
|
+
required: [items]
|
|
1163
|
+
properties:
|
|
1164
|
+
items:
|
|
1165
|
+
type: array
|
|
1166
|
+
items: { $ref: '#/components/schemas/EnrollmentToken' }
|
|
1167
|
+
post:
|
|
1168
|
+
tags: [enrollment]
|
|
1169
|
+
summary: Issue a new enrollment token.
|
|
1170
|
+
operationId: enrollment_create_token
|
|
1171
|
+
requestBody:
|
|
1172
|
+
required: true
|
|
1173
|
+
content:
|
|
1174
|
+
application/json:
|
|
1175
|
+
schema: { $ref: '#/components/schemas/EnrollmentTokenCreateRequest' }
|
|
1176
|
+
responses:
|
|
1177
|
+
'201':
|
|
1178
|
+
description: Token issued.
|
|
1179
|
+
content:
|
|
1180
|
+
application/json:
|
|
1181
|
+
schema: { $ref: '#/components/schemas/EnrollmentTokenWithSecret' }
|
|
1182
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1183
|
+
/enrollment-tokens/redeem:
|
|
1184
|
+
post:
|
|
1185
|
+
tags: [enrollment]
|
|
1186
|
+
summary: Redeem an enrollment token for an mTLS identity.
|
|
1187
|
+
operationId: enrollment_redeem
|
|
1188
|
+
description: |
|
|
1189
|
+
The client agent calls this endpoint with the bootstrap token it
|
|
1190
|
+
received out-of-band and a CSR; the response carries the signed
|
|
1191
|
+
client certificate plus the issued client_id.
|
|
1192
|
+
requestBody:
|
|
1193
|
+
required: true
|
|
1194
|
+
content:
|
|
1195
|
+
application/json:
|
|
1196
|
+
schema: { $ref: '#/components/schemas/EnrollmentRedeemRequest' }
|
|
1197
|
+
responses:
|
|
1198
|
+
'200':
|
|
1199
|
+
description: mTLS identity issued.
|
|
1200
|
+
content:
|
|
1201
|
+
application/json:
|
|
1202
|
+
schema: { $ref: '#/components/schemas/EnrollmentRedeemResponse' }
|
|
1203
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1204
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1205
|
+
/enrollment-tokens/{token_id}:
|
|
1206
|
+
parameters:
|
|
1207
|
+
- in: path
|
|
1208
|
+
name: token_id
|
|
1209
|
+
required: true
|
|
1210
|
+
schema: { type: string, format: uuid }
|
|
1211
|
+
delete:
|
|
1212
|
+
tags: [enrollment]
|
|
1213
|
+
summary: Revoke an enrollment token.
|
|
1214
|
+
operationId: enrollment_delete_token
|
|
1215
|
+
responses:
|
|
1216
|
+
'204':
|
|
1217
|
+
description: Token revoked.
|
|
1218
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1219
|
+
/enrollment-tokens/clients/{client_id}/revoke:
|
|
1220
|
+
parameters:
|
|
1221
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1222
|
+
post:
|
|
1223
|
+
tags: [enrollment]
|
|
1224
|
+
summary: Revoke an enrolled client's mTLS identity.
|
|
1225
|
+
operationId: enrollment_revoke_client
|
|
1226
|
+
responses:
|
|
1227
|
+
'200':
|
|
1228
|
+
description: Client revoked.
|
|
1229
|
+
content:
|
|
1230
|
+
application/json:
|
|
1231
|
+
schema: { $ref: '#/components/schemas/Client' }
|
|
1232
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1233
|
+
|
|
1234
|
+
# =====================================================================
|
|
1235
|
+
# agents
|
|
1236
|
+
# =====================================================================
|
|
1237
|
+
|
|
1238
|
+
/clients:inventory:
|
|
1239
|
+
get:
|
|
1240
|
+
tags: [agents]
|
|
1241
|
+
summary: Cross-fleet inventory of clients.
|
|
1242
|
+
operationId: agents_get_client_inventory
|
|
1243
|
+
responses:
|
|
1244
|
+
'200':
|
|
1245
|
+
description: Inventory snapshot.
|
|
1246
|
+
content:
|
|
1247
|
+
application/json:
|
|
1248
|
+
schema: { $ref: '#/components/schemas/ClientInventory' }
|
|
1249
|
+
/clients/{client_id}/capabilities:
|
|
1250
|
+
parameters:
|
|
1251
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1252
|
+
get:
|
|
1253
|
+
tags: [agents]
|
|
1254
|
+
summary: Read advertised capabilities for a client.
|
|
1255
|
+
operationId: agents_get_client_capabilities
|
|
1256
|
+
responses:
|
|
1257
|
+
'200':
|
|
1258
|
+
description: Capabilities.
|
|
1259
|
+
content:
|
|
1260
|
+
application/json:
|
|
1261
|
+
schema: { $ref: '#/components/schemas/ClientCapabilities' }
|
|
1262
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1263
|
+
/agent-rollouts:
|
|
1264
|
+
post:
|
|
1265
|
+
tags: [agents]
|
|
1266
|
+
summary: Create an agent rollout plan.
|
|
1267
|
+
operationId: agents_create_rollout
|
|
1268
|
+
requestBody:
|
|
1269
|
+
required: true
|
|
1270
|
+
content:
|
|
1271
|
+
application/json:
|
|
1272
|
+
schema: { $ref: '#/components/schemas/AgentRolloutCreateRequest' }
|
|
1273
|
+
responses:
|
|
1274
|
+
'201':
|
|
1275
|
+
description: Rollout created.
|
|
1276
|
+
content:
|
|
1277
|
+
application/json:
|
|
1278
|
+
schema: { $ref: '#/components/schemas/AgentRollout' }
|
|
1279
|
+
/agent-rollouts/{rollout_id}:start:
|
|
1280
|
+
parameters:
|
|
1281
|
+
- in: path
|
|
1282
|
+
name: rollout_id
|
|
1283
|
+
required: true
|
|
1284
|
+
schema: { type: string, format: uuid }
|
|
1285
|
+
post:
|
|
1286
|
+
tags: [agents]
|
|
1287
|
+
summary: Start an agent rollout.
|
|
1288
|
+
operationId: agents_start_rollout
|
|
1289
|
+
responses:
|
|
1290
|
+
'200':
|
|
1291
|
+
description: Rollout started.
|
|
1292
|
+
content:
|
|
1293
|
+
application/json:
|
|
1294
|
+
schema: { $ref: '#/components/schemas/AgentRollout' }
|
|
1295
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1296
|
+
|
|
1297
|
+
# ---------------------------------------------------------------------
|
|
1298
|
+
# Client pull surface — PR p19g (renamed to /clients/ in p19h)
|
|
1299
|
+
# ---------------------------------------------------------------------
|
|
1300
|
+
|
|
1301
|
+
/clients/{client_id}/dispatches:next:
|
|
1302
|
+
parameters:
|
|
1303
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1304
|
+
get:
|
|
1305
|
+
tags: [clients]
|
|
1306
|
+
summary: Long-poll for the next signed dispatch envelope.
|
|
1307
|
+
operationId: clients_dispatches_next
|
|
1308
|
+
x-pychestrate-status: planned
|
|
1309
|
+
description: |
|
|
1310
|
+
Long-polling pull endpoint the pod-client calls to fetch
|
|
1311
|
+
the next signed envelope queued for it by the server
|
|
1312
|
+
dispatcher.
|
|
1313
|
+
|
|
1314
|
+
The server blocks the request for up to `timeout_seconds`
|
|
1315
|
+
(default 30, hard cap 60). On a queued dispatch the server
|
|
1316
|
+
returns 200 with a `PendingDispatch` body and the record
|
|
1317
|
+
is dequeued — the client MUST then POST
|
|
1318
|
+
`/runs/{run_id}/dispatch-ack` before the embedded
|
|
1319
|
+
`ack_deadline_at`, otherwise the dispatch reconciler will
|
|
1320
|
+
roll the attempt back. On a timeout the server returns 204
|
|
1321
|
+
and the client immediately re-polls.
|
|
1322
|
+
|
|
1323
|
+
The cert common-name nginx forwards must match the path
|
|
1324
|
+
`client_id` — a mismatch returns 403.
|
|
1325
|
+
security:
|
|
1326
|
+
- pychestrate_mtls: []
|
|
1327
|
+
parameters:
|
|
1328
|
+
- in: query
|
|
1329
|
+
name: timeout_seconds
|
|
1330
|
+
description: How long the server may block before returning 204.
|
|
1331
|
+
schema:
|
|
1332
|
+
type: integer
|
|
1333
|
+
minimum: 0
|
|
1334
|
+
maximum: 60
|
|
1335
|
+
default: 30
|
|
1336
|
+
responses:
|
|
1337
|
+
'200':
|
|
1338
|
+
description: Dispatch dequeued for this client.
|
|
1339
|
+
content:
|
|
1340
|
+
application/json:
|
|
1341
|
+
schema: { $ref: '#/components/schemas/PendingDispatch' }
|
|
1342
|
+
'204':
|
|
1343
|
+
description: No dispatch became available within `timeout_seconds`.
|
|
1344
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1345
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
1346
|
+
'422': { $ref: '#/components/responses/Unprocessable' }
|
|
1347
|
+
'503': { $ref: '#/components/responses/ServiceUnavailable' }
|
|
1348
|
+
|
|
1349
|
+
/clients/{client_id}/heartbeat:
|
|
1350
|
+
parameters:
|
|
1351
|
+
- $ref: '#/components/parameters/ClientIdParam'
|
|
1352
|
+
get:
|
|
1353
|
+
tags: [clients]
|
|
1354
|
+
summary: Pod-client liveness ping.
|
|
1355
|
+
operationId: clients_heartbeat
|
|
1356
|
+
x-pychestrate-status: planned
|
|
1357
|
+
description: |
|
|
1358
|
+
Lightweight ping the pod-client calls between long-poll
|
|
1359
|
+
cycles to keep its registry row `last_seen` current.
|
|
1360
|
+
Returns 204 unconditionally on a successful mTLS
|
|
1361
|
+
handshake; rows missing from the registry are logged
|
|
1362
|
+
server-side and still answered 204.
|
|
1363
|
+
security:
|
|
1364
|
+
- pychestrate_mtls: []
|
|
1365
|
+
responses:
|
|
1366
|
+
'204':
|
|
1367
|
+
description: Heartbeat recorded.
|
|
1368
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1369
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
1370
|
+
'422': { $ref: '#/components/responses/Unprocessable' }
|
|
1371
|
+
|
|
1372
|
+
# =====================================================================
|
|
1373
|
+
# pods (nested under /projects/{project_id})
|
|
1374
|
+
# =====================================================================
|
|
1375
|
+
|
|
1376
|
+
/projects/{project_id}/pod-definitions:
|
|
1377
|
+
parameters:
|
|
1378
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1379
|
+
get:
|
|
1380
|
+
tags: [pods]
|
|
1381
|
+
summary: List pod definitions for a project.
|
|
1382
|
+
operationId: pods_list_definitions
|
|
1383
|
+
responses:
|
|
1384
|
+
'200':
|
|
1385
|
+
description: Pod definitions.
|
|
1386
|
+
content:
|
|
1387
|
+
application/json:
|
|
1388
|
+
schema:
|
|
1389
|
+
type: object
|
|
1390
|
+
required: [items]
|
|
1391
|
+
properties:
|
|
1392
|
+
items:
|
|
1393
|
+
type: array
|
|
1394
|
+
items: { $ref: '#/components/schemas/PodDefinition' }
|
|
1395
|
+
post:
|
|
1396
|
+
tags: [pods]
|
|
1397
|
+
summary: Create a pod definition.
|
|
1398
|
+
operationId: pods_create_definition
|
|
1399
|
+
requestBody:
|
|
1400
|
+
required: true
|
|
1401
|
+
content:
|
|
1402
|
+
application/json:
|
|
1403
|
+
schema: { $ref: '#/components/schemas/PodDefinitionCreateRequest' }
|
|
1404
|
+
responses:
|
|
1405
|
+
'201':
|
|
1406
|
+
description: Pod definition created.
|
|
1407
|
+
content:
|
|
1408
|
+
application/json:
|
|
1409
|
+
schema: { $ref: '#/components/schemas/PodDefinition' }
|
|
1410
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1411
|
+
/projects/{project_id}/pod-definitions/{pod_id}:
|
|
1412
|
+
parameters:
|
|
1413
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1414
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1415
|
+
get:
|
|
1416
|
+
tags: [pods]
|
|
1417
|
+
summary: Read a pod definition.
|
|
1418
|
+
operationId: pods_get_definition
|
|
1419
|
+
responses:
|
|
1420
|
+
'200':
|
|
1421
|
+
description: Pod definition.
|
|
1422
|
+
content:
|
|
1423
|
+
application/json:
|
|
1424
|
+
schema: { $ref: '#/components/schemas/PodDefinition' }
|
|
1425
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1426
|
+
/projects/{project_id}/pods/{pod_id}/state:
|
|
1427
|
+
parameters:
|
|
1428
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1429
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1430
|
+
get:
|
|
1431
|
+
tags: [pods]
|
|
1432
|
+
summary: Read a pod's current lifecycle state.
|
|
1433
|
+
operationId: pods_get_state
|
|
1434
|
+
responses:
|
|
1435
|
+
'200':
|
|
1436
|
+
description: Pod state.
|
|
1437
|
+
content:
|
|
1438
|
+
application/json:
|
|
1439
|
+
schema: { $ref: '#/components/schemas/PodState' }
|
|
1440
|
+
/projects/{project_id}/pods/{pod_id}/state/history:
|
|
1441
|
+
parameters:
|
|
1442
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1443
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1444
|
+
get:
|
|
1445
|
+
tags: [pods]
|
|
1446
|
+
summary: Read a pod's lifecycle state history.
|
|
1447
|
+
operationId: pods_get_state_history
|
|
1448
|
+
responses:
|
|
1449
|
+
'200':
|
|
1450
|
+
description: State history.
|
|
1451
|
+
content:
|
|
1452
|
+
application/json:
|
|
1453
|
+
schema:
|
|
1454
|
+
type: object
|
|
1455
|
+
required: [items]
|
|
1456
|
+
properties:
|
|
1457
|
+
items:
|
|
1458
|
+
type: array
|
|
1459
|
+
items: { $ref: '#/components/schemas/PodStateTransition' }
|
|
1460
|
+
/projects/{project_id}/pods/{pod_id}/registration:
|
|
1461
|
+
parameters:
|
|
1462
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1463
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1464
|
+
get:
|
|
1465
|
+
tags: [pods]
|
|
1466
|
+
summary: Read the latest pod registration.
|
|
1467
|
+
operationId: pods_get_registration
|
|
1468
|
+
responses:
|
|
1469
|
+
'200':
|
|
1470
|
+
description: Registration.
|
|
1471
|
+
content:
|
|
1472
|
+
application/json:
|
|
1473
|
+
schema: { $ref: '#/components/schemas/PodRegistration' }
|
|
1474
|
+
/projects/{project_id}/pods/{pod_id}/registrations:
|
|
1475
|
+
parameters:
|
|
1476
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1477
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1478
|
+
get:
|
|
1479
|
+
tags: [pods]
|
|
1480
|
+
summary: List all historical pod registrations.
|
|
1481
|
+
operationId: pods_list_registrations
|
|
1482
|
+
responses:
|
|
1483
|
+
'200':
|
|
1484
|
+
description: Registrations.
|
|
1485
|
+
content:
|
|
1486
|
+
application/json:
|
|
1487
|
+
schema:
|
|
1488
|
+
type: object
|
|
1489
|
+
required: [items]
|
|
1490
|
+
properties:
|
|
1491
|
+
items:
|
|
1492
|
+
type: array
|
|
1493
|
+
items: { $ref: '#/components/schemas/PodRegistration' }
|
|
1494
|
+
/projects/{project_id}/pods/{pod_id}/topology:
|
|
1495
|
+
parameters:
|
|
1496
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1497
|
+
- $ref: '#/components/parameters/PodIdParam'
|
|
1498
|
+
get:
|
|
1499
|
+
tags: [pods]
|
|
1500
|
+
summary: Read a pod's topology graph.
|
|
1501
|
+
operationId: pods_get_topology
|
|
1502
|
+
responses:
|
|
1503
|
+
'200':
|
|
1504
|
+
description: Topology.
|
|
1505
|
+
content:
|
|
1506
|
+
application/json:
|
|
1507
|
+
schema: { $ref: '#/components/schemas/PodTopology' }
|
|
1508
|
+
/projects/{project_id}/pod-templates:
|
|
1509
|
+
parameters:
|
|
1510
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1511
|
+
get:
|
|
1512
|
+
tags: [pods]
|
|
1513
|
+
summary: List pod templates.
|
|
1514
|
+
operationId: pods_list_templates
|
|
1515
|
+
responses:
|
|
1516
|
+
'200':
|
|
1517
|
+
description: Templates.
|
|
1518
|
+
content:
|
|
1519
|
+
application/json:
|
|
1520
|
+
schema:
|
|
1521
|
+
type: object
|
|
1522
|
+
required: [items]
|
|
1523
|
+
properties:
|
|
1524
|
+
items:
|
|
1525
|
+
type: array
|
|
1526
|
+
items: { $ref: '#/components/schemas/PodTemplate' }
|
|
1527
|
+
post:
|
|
1528
|
+
tags: [pods]
|
|
1529
|
+
summary: Create a pod template.
|
|
1530
|
+
operationId: pods_create_template
|
|
1531
|
+
requestBody:
|
|
1532
|
+
required: true
|
|
1533
|
+
content:
|
|
1534
|
+
application/json:
|
|
1535
|
+
schema: { $ref: '#/components/schemas/PodTemplateCreateRequest' }
|
|
1536
|
+
responses:
|
|
1537
|
+
'201':
|
|
1538
|
+
description: Template created.
|
|
1539
|
+
content:
|
|
1540
|
+
application/json:
|
|
1541
|
+
schema: { $ref: '#/components/schemas/PodTemplate' }
|
|
1542
|
+
/projects/{project_id}/pod-templates/{template_id}:materialize:
|
|
1543
|
+
parameters:
|
|
1544
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1545
|
+
- in: path
|
|
1546
|
+
name: template_id
|
|
1547
|
+
required: true
|
|
1548
|
+
schema: { type: string }
|
|
1549
|
+
post:
|
|
1550
|
+
tags: [pods]
|
|
1551
|
+
summary: Materialize a pod from a template.
|
|
1552
|
+
operationId: pods_materialize_template
|
|
1553
|
+
requestBody:
|
|
1554
|
+
required: true
|
|
1555
|
+
content:
|
|
1556
|
+
application/json:
|
|
1557
|
+
schema: { $ref: '#/components/schemas/PodTemplateMaterializeRequest' }
|
|
1558
|
+
responses:
|
|
1559
|
+
'202':
|
|
1560
|
+
description: Materialization accepted (async).
|
|
1561
|
+
content:
|
|
1562
|
+
application/json:
|
|
1563
|
+
schema: { $ref: '#/components/schemas/PodDefinition' }
|
|
1564
|
+
/projects/{project_id}/pod-templates/{template_id}/parameters:
|
|
1565
|
+
parameters:
|
|
1566
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
1567
|
+
- in: path
|
|
1568
|
+
name: template_id
|
|
1569
|
+
required: true
|
|
1570
|
+
schema: { type: string }
|
|
1571
|
+
put:
|
|
1572
|
+
tags: [pods]
|
|
1573
|
+
summary: Update template parameters.
|
|
1574
|
+
operationId: pods_update_template_parameters
|
|
1575
|
+
requestBody:
|
|
1576
|
+
required: true
|
|
1577
|
+
content:
|
|
1578
|
+
application/json:
|
|
1579
|
+
schema: { type: object }
|
|
1580
|
+
responses:
|
|
1581
|
+
'200':
|
|
1582
|
+
description: Parameters updated.
|
|
1583
|
+
content:
|
|
1584
|
+
application/json:
|
|
1585
|
+
schema: { $ref: '#/components/schemas/PodTemplate' }
|
|
1586
|
+
delete:
|
|
1587
|
+
tags: [pods]
|
|
1588
|
+
summary: Delete a pod template.
|
|
1589
|
+
operationId: pods_delete_template
|
|
1590
|
+
responses:
|
|
1591
|
+
'204':
|
|
1592
|
+
description: Template deleted.
|
|
1593
|
+
|
|
1594
|
+
# =====================================================================
|
|
1595
|
+
# plugin_api
|
|
1596
|
+
# =====================================================================
|
|
1597
|
+
|
|
1598
|
+
/plugin-api/_meta/catalog:
|
|
1599
|
+
get:
|
|
1600
|
+
tags: [plugin_api]
|
|
1601
|
+
summary: List the plugin-API protocol catalog.
|
|
1602
|
+
operationId: plugin_api_list_catalog
|
|
1603
|
+
responses:
|
|
1604
|
+
'200':
|
|
1605
|
+
description: Catalog.
|
|
1606
|
+
content:
|
|
1607
|
+
application/json:
|
|
1608
|
+
schema:
|
|
1609
|
+
type: object
|
|
1610
|
+
required: [items]
|
|
1611
|
+
properties:
|
|
1612
|
+
items:
|
|
1613
|
+
type: array
|
|
1614
|
+
items: { $ref: '#/components/schemas/PluginApiProtocol' }
|
|
1615
|
+
/plugin-api/_meta/catalog/{protocol_name}:
|
|
1616
|
+
parameters:
|
|
1617
|
+
- in: path
|
|
1618
|
+
name: protocol_name
|
|
1619
|
+
required: true
|
|
1620
|
+
schema: { type: string }
|
|
1621
|
+
get:
|
|
1622
|
+
tags: [plugin_api]
|
|
1623
|
+
summary: Read one plugin-API protocol's metadata.
|
|
1624
|
+
operationId: plugin_api_get_protocol
|
|
1625
|
+
responses:
|
|
1626
|
+
'200':
|
|
1627
|
+
description: Protocol metadata.
|
|
1628
|
+
content:
|
|
1629
|
+
application/json:
|
|
1630
|
+
schema: { $ref: '#/components/schemas/PluginApiProtocol' }
|
|
1631
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1632
|
+
/plugin-api/_meta/mounts:
|
|
1633
|
+
get:
|
|
1634
|
+
tags: [plugin_api]
|
|
1635
|
+
summary: List extension mounts.
|
|
1636
|
+
operationId: plugin_api_list_mounts
|
|
1637
|
+
responses:
|
|
1638
|
+
'200':
|
|
1639
|
+
description: Mounts.
|
|
1640
|
+
content:
|
|
1641
|
+
application/json:
|
|
1642
|
+
schema:
|
|
1643
|
+
type: object
|
|
1644
|
+
required: [items]
|
|
1645
|
+
properties:
|
|
1646
|
+
items:
|
|
1647
|
+
type: array
|
|
1648
|
+
items: { $ref: '#/components/schemas/PluginApiMount' }
|
|
1649
|
+
|
|
1650
|
+
# =====================================================================
|
|
1651
|
+
# PLANNED — runs / test_runs / test_catalog / progress / dispatch /
|
|
1652
|
+
# results / artifacts
|
|
1653
|
+
#
|
|
1654
|
+
# These endpoints are NOT yet implemented in the codebase. They are
|
|
1655
|
+
# declared here so that:
|
|
1656
|
+
# 1. The generated client surfaces them as soon as the spec lands.
|
|
1657
|
+
# 2. Parallel implementation PRs target a stable, agreed-upon shape.
|
|
1658
|
+
#
|
|
1659
|
+
# Each operation carries `x-pychestrate-status: planned`.
|
|
1660
|
+
# =====================================================================
|
|
1661
|
+
|
|
1662
|
+
/runs:
|
|
1663
|
+
get:
|
|
1664
|
+
tags: [runs]
|
|
1665
|
+
summary: List runs (with filters).
|
|
1666
|
+
operationId: runs_list
|
|
1667
|
+
x-pychestrate-status: planned
|
|
1668
|
+
parameters:
|
|
1669
|
+
- in: query
|
|
1670
|
+
name: project_id
|
|
1671
|
+
schema: { type: string, format: uuid }
|
|
1672
|
+
- in: query
|
|
1673
|
+
name: pod_id
|
|
1674
|
+
schema: { type: string, format: uuid }
|
|
1675
|
+
- in: query
|
|
1676
|
+
name: state
|
|
1677
|
+
schema: { $ref: '#/components/schemas/RunState' }
|
|
1678
|
+
- $ref: '#/components/parameters/PageParam'
|
|
1679
|
+
- $ref: '#/components/parameters/PerPageParam'
|
|
1680
|
+
responses:
|
|
1681
|
+
'200':
|
|
1682
|
+
description: Page of runs.
|
|
1683
|
+
content:
|
|
1684
|
+
application/json:
|
|
1685
|
+
schema: { $ref: '#/components/schemas/RunPage' }
|
|
1686
|
+
/runs/{run_id}:
|
|
1687
|
+
parameters:
|
|
1688
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1689
|
+
get:
|
|
1690
|
+
tags: [runs]
|
|
1691
|
+
summary: Read a run.
|
|
1692
|
+
operationId: runs_get_by_id
|
|
1693
|
+
x-pychestrate-status: planned
|
|
1694
|
+
responses:
|
|
1695
|
+
'200':
|
|
1696
|
+
description: Run.
|
|
1697
|
+
content:
|
|
1698
|
+
application/json:
|
|
1699
|
+
schema: { $ref: '#/components/schemas/Run' }
|
|
1700
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1701
|
+
/runs/{run_id}/events:
|
|
1702
|
+
parameters:
|
|
1703
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1704
|
+
get:
|
|
1705
|
+
tags: [runs]
|
|
1706
|
+
summary: Subscribe to run events via Server-Sent Events.
|
|
1707
|
+
operationId: runs_subscribe_events
|
|
1708
|
+
x-pychestrate-status: planned
|
|
1709
|
+
description: |
|
|
1710
|
+
Long-lived response stream encoded as `text/event-stream`
|
|
1711
|
+
(Server-Sent Events). Each SSE frame's `data:` payload is a
|
|
1712
|
+
JSON document conforming to the `RunEvent` schema; the SSE
|
|
1713
|
+
`event:` line carries the event type
|
|
1714
|
+
(`state_changed` | `progress` | `result_manifest` | `heartbeat`).
|
|
1715
|
+
|
|
1716
|
+
Clients should send `Accept: text/event-stream` and reconnect
|
|
1717
|
+
with `Last-Event-ID` to resume from a known sequence number.
|
|
1718
|
+
responses:
|
|
1719
|
+
'200':
|
|
1720
|
+
description: SSE stream of run events.
|
|
1721
|
+
content:
|
|
1722
|
+
text/event-stream:
|
|
1723
|
+
schema: { $ref: '#/components/schemas/RunEvent' }
|
|
1724
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1725
|
+
|
|
1726
|
+
/runs/{run_id}/dispatch-ack:
|
|
1727
|
+
parameters:
|
|
1728
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1729
|
+
post:
|
|
1730
|
+
tags: [dispatch]
|
|
1731
|
+
summary: Client agent acknowledges receipt of a dispatched envelope.
|
|
1732
|
+
operationId: dispatch_ack_run
|
|
1733
|
+
x-pychestrate-status: planned
|
|
1734
|
+
description: |
|
|
1735
|
+
Posted by the pod client agent over mTLS after it has accepted
|
|
1736
|
+
a dispatch and minted a local run identifier. Signals that the
|
|
1737
|
+
envelope was received and is being prepared for execution; the
|
|
1738
|
+
server transitions the run's state from `DISPATCHING` to
|
|
1739
|
+
`DISPATCHED` on success.
|
|
1740
|
+
security:
|
|
1741
|
+
- pychestrate_mtls: []
|
|
1742
|
+
requestBody:
|
|
1743
|
+
required: true
|
|
1744
|
+
content:
|
|
1745
|
+
application/json:
|
|
1746
|
+
schema: { $ref: '#/components/schemas/DispatchAck' }
|
|
1747
|
+
responses:
|
|
1748
|
+
'204':
|
|
1749
|
+
description: Ack recorded.
|
|
1750
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1751
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1752
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1753
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
1754
|
+
|
|
1755
|
+
/runs/{run_id}/progress:
|
|
1756
|
+
parameters:
|
|
1757
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1758
|
+
post:
|
|
1759
|
+
tags: [progress]
|
|
1760
|
+
summary: Ingest a live-progress frame for a run.
|
|
1761
|
+
operationId: progress_post_frame
|
|
1762
|
+
x-pychestrate-status: planned
|
|
1763
|
+
description: |
|
|
1764
|
+
Posted by the pod client agent over mTLS at intervals during the
|
|
1765
|
+
run. Payload shape matches the `codecraftai-testbase`
|
|
1766
|
+
`ProgressConcentratorServer` / `ForwardingInfo` contract so an
|
|
1767
|
+
unmodified testbase client can post directly without
|
|
1768
|
+
translation.
|
|
1769
|
+
|
|
1770
|
+
Returns 202 on accept; the ingester is passive and never blocks
|
|
1771
|
+
the caller. Errors in downstream parsing are surfaced via the
|
|
1772
|
+
`RunEvent` SSE stream rather than the HTTP response.
|
|
1773
|
+
security:
|
|
1774
|
+
- pychestrate_mtls: []
|
|
1775
|
+
requestBody:
|
|
1776
|
+
required: true
|
|
1777
|
+
content:
|
|
1778
|
+
application/json:
|
|
1779
|
+
schema: { $ref: '#/components/schemas/ProgressFrame' }
|
|
1780
|
+
responses:
|
|
1781
|
+
'202':
|
|
1782
|
+
description: Progress frame accepted.
|
|
1783
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1784
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1785
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1786
|
+
|
|
1787
|
+
/runs/{run_id}/results/complete:
|
|
1788
|
+
parameters:
|
|
1789
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1790
|
+
post:
|
|
1791
|
+
tags: [results]
|
|
1792
|
+
summary: Signal that the result tree for a run is fully uploaded.
|
|
1793
|
+
operationId: results_post_complete
|
|
1794
|
+
x-pychestrate-status: planned
|
|
1795
|
+
description: |
|
|
1796
|
+
Posted by the pod client agent over mTLS once it has finished
|
|
1797
|
+
writing the testbase result tree (`testrun_summary.json`,
|
|
1798
|
+
`testrun_results_stream.jsos`, `testrun_results.json`,
|
|
1799
|
+
`testsummary.html`, plus the `artifacts/`, `taskings/`,
|
|
1800
|
+
`tc-by-products/`, `diagnostics/` subtrees) to the shared
|
|
1801
|
+
results backend.
|
|
1802
|
+
|
|
1803
|
+
The server transitions the run to a terminal state
|
|
1804
|
+
(`COMPLETED` / `FAILED`) based on the manifest's
|
|
1805
|
+
`terminal_outcome` field and kicks off the
|
|
1806
|
+
`result_tree_parser` extension chain.
|
|
1807
|
+
security:
|
|
1808
|
+
- pychestrate_mtls: []
|
|
1809
|
+
requestBody:
|
|
1810
|
+
required: true
|
|
1811
|
+
content:
|
|
1812
|
+
application/json:
|
|
1813
|
+
schema: { $ref: '#/components/schemas/ResultManifest' }
|
|
1814
|
+
responses:
|
|
1815
|
+
'202':
|
|
1816
|
+
description: Manifest accepted; result-tree parsing scheduled.
|
|
1817
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1818
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1819
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1820
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
1821
|
+
|
|
1822
|
+
/envelope-signing-keys:
|
|
1823
|
+
get:
|
|
1824
|
+
tags: [dispatch]
|
|
1825
|
+
summary: List the Ed25519 public keys used to sign job envelopes.
|
|
1826
|
+
operationId: envelope_signing_keys_list
|
|
1827
|
+
x-pychestrate-status: planned
|
|
1828
|
+
description: |
|
|
1829
|
+
A JWKS-style public-key distribution endpoint. The pod-client
|
|
1830
|
+
agent calls this to obtain the Ed25519 public key(s) it must
|
|
1831
|
+
verify signed job envelopes against — without the operator
|
|
1832
|
+
installing the key out-of-band.
|
|
1833
|
+
|
|
1834
|
+
Auth: a signing *public* key is not a secret, so the endpoint
|
|
1835
|
+
accepts **any authenticated principal** — a cookie session, an
|
|
1836
|
+
API key, OR a verified mTLS client certificate. A
|
|
1837
|
+
freshly-enrolled pod client already holds its mTLS identity, so
|
|
1838
|
+
the mTLS path covers the client's use case. Anonymous requests
|
|
1839
|
+
get a 401. The endpoint returns 503 when no signing key has
|
|
1840
|
+
been provisioned.
|
|
1841
|
+
|
|
1842
|
+
The response carries one or more keys to support rotation: at
|
|
1843
|
+
most one key is `active` and any others are `retiring`
|
|
1844
|
+
(grace-window verify keys). The same key set is also bundled in
|
|
1845
|
+
the `POST /enrollment-tokens/redeem` response so the happy path
|
|
1846
|
+
needs zero extra calls.
|
|
1847
|
+
responses:
|
|
1848
|
+
'200':
|
|
1849
|
+
description: The active envelope-signing public-key set.
|
|
1850
|
+
content:
|
|
1851
|
+
application/json:
|
|
1852
|
+
schema: { $ref: '#/components/schemas/SigningKeySet' }
|
|
1853
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1854
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
1855
|
+
'503': { $ref: '#/components/responses/ServiceUnavailable' }
|
|
1856
|
+
|
|
1857
|
+
/runs/{run_id}/artifacts:
|
|
1858
|
+
parameters:
|
|
1859
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1860
|
+
get:
|
|
1861
|
+
tags: [artifacts]
|
|
1862
|
+
summary: List artifacts for a run.
|
|
1863
|
+
operationId: artifacts_list_for_run
|
|
1864
|
+
x-pychestrate-status: planned
|
|
1865
|
+
parameters:
|
|
1866
|
+
- in: query
|
|
1867
|
+
name: prefix
|
|
1868
|
+
description: Limit results to artifacts whose path starts with this prefix.
|
|
1869
|
+
schema: { type: string }
|
|
1870
|
+
- $ref: '#/components/parameters/PageParam'
|
|
1871
|
+
- $ref: '#/components/parameters/PerPageParam'
|
|
1872
|
+
responses:
|
|
1873
|
+
'200':
|
|
1874
|
+
description: Page of artifact entries.
|
|
1875
|
+
content:
|
|
1876
|
+
application/json:
|
|
1877
|
+
schema: { $ref: '#/components/schemas/ArtifactEntryPage' }
|
|
1878
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1879
|
+
/runs/{run_id}/artifacts/{artifact_path}:
|
|
1880
|
+
parameters:
|
|
1881
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1882
|
+
- in: path
|
|
1883
|
+
name: artifact_path
|
|
1884
|
+
required: true
|
|
1885
|
+
description: Slash-delimited artifact path relative to the run's results root.
|
|
1886
|
+
schema: { type: string }
|
|
1887
|
+
get:
|
|
1888
|
+
tags: [artifacts]
|
|
1889
|
+
summary: Read metadata for one artifact entry.
|
|
1890
|
+
operationId: artifacts_get_entry
|
|
1891
|
+
x-pychestrate-status: planned
|
|
1892
|
+
responses:
|
|
1893
|
+
'200':
|
|
1894
|
+
description: Artifact entry.
|
|
1895
|
+
content:
|
|
1896
|
+
application/json:
|
|
1897
|
+
schema: { $ref: '#/components/schemas/ArtifactEntry' }
|
|
1898
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1899
|
+
|
|
1900
|
+
/runs/{run_id}/artifacts:presign:
|
|
1901
|
+
parameters:
|
|
1902
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1903
|
+
post:
|
|
1904
|
+
tags: [results]
|
|
1905
|
+
summary: Mint presigned PUT URLs for a run's result-tree upload.
|
|
1906
|
+
operationId: artifacts_presign_upload
|
|
1907
|
+
x-pychestrate-status: planned
|
|
1908
|
+
description: |
|
|
1909
|
+
Posted by the pod client agent over mTLS to obtain
|
|
1910
|
+
short-lived presigned PUT URLs for the relative artifact
|
|
1911
|
+
paths it discovered while walking its testbase result tree.
|
|
1912
|
+
|
|
1913
|
+
The agent PUTs each artifact's bytes directly to the
|
|
1914
|
+
returned `put_url`, then calls
|
|
1915
|
+
`POST /runs/{run_id}/results/complete`. Object-store
|
|
1916
|
+
credentials never leave the server -- the credentialed
|
|
1917
|
+
transfer is encoded entirely in the presigned URL.
|
|
1918
|
+
|
|
1919
|
+
On the dev / alpha deploy there is no S3-compatible object
|
|
1920
|
+
store, so the server mints **local-filesystem fallback**
|
|
1921
|
+
URLs that point back at
|
|
1922
|
+
`PUT /runs/{run_id}/artifacts/_local-upload`. The URL
|
|
1923
|
+
carries an HMAC signature over `run_id` + path + expiry, so
|
|
1924
|
+
the fallback needs no object-store credential while staying
|
|
1925
|
+
tamper-proof. The agent's code path is identical either way.
|
|
1926
|
+
|
|
1927
|
+
Auth: the verified mTLS client common-name must match the
|
|
1928
|
+
`client_id` that owns the run's current dispatch receipt --
|
|
1929
|
+
only the client running the job may upload its results. A
|
|
1930
|
+
run that has already reached a terminal state no longer
|
|
1931
|
+
accepts uploads (409).
|
|
1932
|
+
security:
|
|
1933
|
+
- pychestrate_mtls: []
|
|
1934
|
+
requestBody:
|
|
1935
|
+
required: true
|
|
1936
|
+
content:
|
|
1937
|
+
application/json:
|
|
1938
|
+
schema: { $ref: '#/components/schemas/ArtifactPresignRequest' }
|
|
1939
|
+
responses:
|
|
1940
|
+
'200':
|
|
1941
|
+
description: Presigned upload targets minted, one per requested path.
|
|
1942
|
+
content:
|
|
1943
|
+
application/json:
|
|
1944
|
+
schema: { $ref: '#/components/schemas/ArtifactPresignResponse' }
|
|
1945
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
1946
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1947
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
1948
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1949
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
1950
|
+
'422': { $ref: '#/components/responses/Unprocessable' }
|
|
1951
|
+
'503': { $ref: '#/components/responses/ServiceUnavailable' }
|
|
1952
|
+
|
|
1953
|
+
/runs/{run_id}/artifacts/_local-upload:
|
|
1954
|
+
parameters:
|
|
1955
|
+
- $ref: '#/components/parameters/RunIdParam'
|
|
1956
|
+
- in: query
|
|
1957
|
+
name: path
|
|
1958
|
+
required: true
|
|
1959
|
+
description: Canonicalized relative artifact path the bytes are stored under.
|
|
1960
|
+
schema: { type: string }
|
|
1961
|
+
- in: query
|
|
1962
|
+
name: expires
|
|
1963
|
+
required: true
|
|
1964
|
+
description: Expiry of the presigned URL as a Unix epoch second.
|
|
1965
|
+
schema: { type: integer }
|
|
1966
|
+
- in: query
|
|
1967
|
+
name: signature
|
|
1968
|
+
required: true
|
|
1969
|
+
description: HMAC-SHA256 signature over run_id + path + expiry.
|
|
1970
|
+
schema: { type: string }
|
|
1971
|
+
put:
|
|
1972
|
+
tags: [results]
|
|
1973
|
+
summary: Upload sink for the local-filesystem presign fallback.
|
|
1974
|
+
operationId: artifacts_local_upload
|
|
1975
|
+
x-pychestrate-status: planned
|
|
1976
|
+
description: |
|
|
1977
|
+
The upload sink the `:presign` operation's local-filesystem
|
|
1978
|
+
fallback URLs point at. Used only when the deploy has no
|
|
1979
|
+
S3-compatible object store.
|
|
1980
|
+
|
|
1981
|
+
The request body is the raw artifact bytes. The handler
|
|
1982
|
+
re-verifies the `signature` query parameter against the
|
|
1983
|
+
server-held HMAC secret before writing the bytes under the
|
|
1984
|
+
run's allocated `backend_key_prefix`. A forged or expired
|
|
1985
|
+
signature is rejected with 403; the signature -- not a
|
|
1986
|
+
permission check -- is this endpoint's auth contract, so no
|
|
1987
|
+
object-store credential is involved.
|
|
1988
|
+
security:
|
|
1989
|
+
- pychestrate_mtls: []
|
|
1990
|
+
requestBody:
|
|
1991
|
+
required: true
|
|
1992
|
+
content:
|
|
1993
|
+
application/octet-stream:
|
|
1994
|
+
schema:
|
|
1995
|
+
type: string
|
|
1996
|
+
format: binary
|
|
1997
|
+
responses:
|
|
1998
|
+
'201':
|
|
1999
|
+
description: Artifact bytes written to the result tree.
|
|
2000
|
+
content:
|
|
2001
|
+
application/json:
|
|
2002
|
+
schema: { $ref: '#/components/schemas/ArtifactLocalUploadResult' }
|
|
2003
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
2004
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2005
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
2006
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
2007
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
2008
|
+
'413': { $ref: '#/components/responses/PayloadTooLarge' }
|
|
2009
|
+
'503': { $ref: '#/components/responses/ServiceUnavailable' }
|
|
2010
|
+
|
|
2011
|
+
/projects/{project_id}/test-runs:
|
|
2012
|
+
parameters:
|
|
2013
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
2014
|
+
post:
|
|
2015
|
+
tags: [test_runs]
|
|
2016
|
+
summary: Trigger a new test run on a project.
|
|
2017
|
+
operationId: test_runs_trigger
|
|
2018
|
+
x-pychestrate-status: planned
|
|
2019
|
+
description: |
|
|
2020
|
+
Creates a new run from a catalog entry and a pod selector.
|
|
2021
|
+
Returns the freshly-created run in `PENDING` state; the
|
|
2022
|
+
dispatcher transitions it to `DISPATCHING` asynchronously.
|
|
2023
|
+
requestBody:
|
|
2024
|
+
required: true
|
|
2025
|
+
content:
|
|
2026
|
+
application/json:
|
|
2027
|
+
schema: { $ref: '#/components/schemas/TestRunCreateRequest' }
|
|
2028
|
+
responses:
|
|
2029
|
+
'202':
|
|
2030
|
+
description: Run accepted (queued for dispatch).
|
|
2031
|
+
content:
|
|
2032
|
+
application/json:
|
|
2033
|
+
schema: { $ref: '#/components/schemas/Run' }
|
|
2034
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
2035
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2036
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
2037
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
2038
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
2039
|
+
|
|
2040
|
+
/projects/{project_id}/test-catalog:
|
|
2041
|
+
parameters:
|
|
2042
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
2043
|
+
get:
|
|
2044
|
+
tags: [test_catalog]
|
|
2045
|
+
summary: List test-catalog entries for a project.
|
|
2046
|
+
operationId: test_catalog_list
|
|
2047
|
+
x-pychestrate-status: planned
|
|
2048
|
+
parameters:
|
|
2049
|
+
- in: query
|
|
2050
|
+
name: importer_tag
|
|
2051
|
+
schema: { type: string }
|
|
2052
|
+
- in: query
|
|
2053
|
+
name: include_retired
|
|
2054
|
+
schema: { type: boolean, default: false }
|
|
2055
|
+
- $ref: '#/components/parameters/PageParam'
|
|
2056
|
+
- $ref: '#/components/parameters/PerPageParam'
|
|
2057
|
+
responses:
|
|
2058
|
+
'200':
|
|
2059
|
+
description: Page of catalog entries.
|
|
2060
|
+
content:
|
|
2061
|
+
application/json:
|
|
2062
|
+
schema: { $ref: '#/components/schemas/TestCatalogEntryPage' }
|
|
2063
|
+
post:
|
|
2064
|
+
tags: [test_catalog]
|
|
2065
|
+
summary: Upsert a test-catalog entry.
|
|
2066
|
+
operationId: test_catalog_upsert
|
|
2067
|
+
x-pychestrate-status: planned
|
|
2068
|
+
requestBody:
|
|
2069
|
+
required: true
|
|
2070
|
+
content:
|
|
2071
|
+
application/json:
|
|
2072
|
+
schema: { $ref: '#/components/schemas/TestCatalogEntryUpsertRequest' }
|
|
2073
|
+
responses:
|
|
2074
|
+
'200':
|
|
2075
|
+
description: Entry upserted.
|
|
2076
|
+
content:
|
|
2077
|
+
application/json:
|
|
2078
|
+
schema: { $ref: '#/components/schemas/TestCatalogEntry' }
|
|
2079
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
2080
|
+
/projects/{project_id}/test-catalog:import:
|
|
2081
|
+
parameters:
|
|
2082
|
+
- $ref: '#/components/parameters/ProjectIdParam'
|
|
2083
|
+
post:
|
|
2084
|
+
tags: [test_catalog]
|
|
2085
|
+
summary: Run a test-catalog importer (e.g., testbase project discovery).
|
|
2086
|
+
operationId: test_catalog_import
|
|
2087
|
+
x-pychestrate-status: planned
|
|
2088
|
+
description: |
|
|
2089
|
+
Invokes the named importer (resolved via the
|
|
2090
|
+
`test_catalog_importer` factory protocol) against the project.
|
|
2091
|
+
Each import overwrites entries with the same
|
|
2092
|
+
`(project_id, importer_tag, entry_key)` key in place.
|
|
2093
|
+
requestBody:
|
|
2094
|
+
required: true
|
|
2095
|
+
content:
|
|
2096
|
+
application/json:
|
|
2097
|
+
schema: { $ref: '#/components/schemas/TestCatalogImportRequest' }
|
|
2098
|
+
responses:
|
|
2099
|
+
'202':
|
|
2100
|
+
description: Import accepted (runs asynchronously).
|
|
2101
|
+
content:
|
|
2102
|
+
application/json:
|
|
2103
|
+
schema: { $ref: '#/components/schemas/TestCatalogImportSummary' }
|
|
2104
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
2105
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
2106
|
+
|
|
2107
|
+
components:
|
|
2108
|
+
|
|
2109
|
+
securitySchemes:
|
|
2110
|
+
pychestrate_session:
|
|
2111
|
+
type: apiKey
|
|
2112
|
+
in: cookie
|
|
2113
|
+
name: pych_session
|
|
2114
|
+
description: |
|
|
2115
|
+
HMAC-signed session cookie issued by the local-login flow at
|
|
2116
|
+
`/login` (HTML form, not part of the REST surface). The
|
|
2117
|
+
webworks `PychestrateSessionMiddleware` resolves this cookie
|
|
2118
|
+
to a request-scoped actor on every request.
|
|
2119
|
+
pychestrate_api_key:
|
|
2120
|
+
type: apiKey
|
|
2121
|
+
in: header
|
|
2122
|
+
name: X-Pychestrate-Api-Key
|
|
2123
|
+
description: |
|
|
2124
|
+
Long-lived API key for tooling and CI integrations. Issued
|
|
2125
|
+
out-of-band through the admin UI.
|
|
2126
|
+
pychestrate_mtls:
|
|
2127
|
+
type: mutualTLS
|
|
2128
|
+
description: |
|
|
2129
|
+
Mutual TLS using the client certificate issued by
|
|
2130
|
+
`POST /enrollment-tokens/redeem`. Required on every endpoint
|
|
2131
|
+
the pod client agent calls (`dispatch-ack`, `progress`,
|
|
2132
|
+
`results/complete`, plus any future client-pull endpoints).
|
|
2133
|
+
|
|
2134
|
+
parameters:
|
|
2135
|
+
|
|
2136
|
+
PageParam:
|
|
2137
|
+
in: query
|
|
2138
|
+
name: page
|
|
2139
|
+
description: One-based page index for paginated list endpoints.
|
|
2140
|
+
schema: { type: integer, minimum: 1, default: 1 }
|
|
2141
|
+
PerPageParam:
|
|
2142
|
+
in: query
|
|
2143
|
+
name: per_page
|
|
2144
|
+
description: Page size for paginated list endpoints.
|
|
2145
|
+
schema: { type: integer, minimum: 1, maximum: 500, default: 50 }
|
|
2146
|
+
UserIdParam:
|
|
2147
|
+
in: path
|
|
2148
|
+
name: user_id
|
|
2149
|
+
required: true
|
|
2150
|
+
schema: { type: string, format: uuid }
|
|
2151
|
+
ProjectIdParam:
|
|
2152
|
+
in: path
|
|
2153
|
+
name: project_id
|
|
2154
|
+
required: true
|
|
2155
|
+
schema: { type: string, format: uuid }
|
|
2156
|
+
ProjectSlugParam:
|
|
2157
|
+
in: path
|
|
2158
|
+
name: slug
|
|
2159
|
+
required: true
|
|
2160
|
+
description: Project slug — kebab-case, globally unique.
|
|
2161
|
+
schema: { type: string, pattern: '^[a-z0-9][a-z0-9-]*$' }
|
|
2162
|
+
ClientIdParam:
|
|
2163
|
+
in: path
|
|
2164
|
+
name: client_id
|
|
2165
|
+
required: true
|
|
2166
|
+
schema: { type: string, format: uuid }
|
|
2167
|
+
PodIdParam:
|
|
2168
|
+
in: path
|
|
2169
|
+
name: pod_id
|
|
2170
|
+
required: true
|
|
2171
|
+
schema: { type: string, format: uuid }
|
|
2172
|
+
RunIdParam:
|
|
2173
|
+
in: path
|
|
2174
|
+
name: run_id
|
|
2175
|
+
required: true
|
|
2176
|
+
schema: { type: string, format: uuid }
|
|
2177
|
+
FlagKeyParam:
|
|
2178
|
+
in: path
|
|
2179
|
+
name: flag_key
|
|
2180
|
+
required: true
|
|
2181
|
+
schema: { type: string }
|
|
2182
|
+
VariableScopeParam:
|
|
2183
|
+
in: path
|
|
2184
|
+
name: scope
|
|
2185
|
+
required: true
|
|
2186
|
+
description: Variable scope kind.
|
|
2187
|
+
schema:
|
|
2188
|
+
type: string
|
|
2189
|
+
enum: [global, project, pod, run]
|
|
2190
|
+
VariableScopeIdParam:
|
|
2191
|
+
in: path
|
|
2192
|
+
name: scope_id
|
|
2193
|
+
required: true
|
|
2194
|
+
schema: { type: string }
|
|
2195
|
+
EntityKindParam:
|
|
2196
|
+
in: path
|
|
2197
|
+
name: entity_kind
|
|
2198
|
+
required: true
|
|
2199
|
+
description: Type of entity the tag is attached to.
|
|
2200
|
+
schema:
|
|
2201
|
+
type: string
|
|
2202
|
+
enum: [project, pod, client, run, resource, artifact, run_artifact]
|
|
2203
|
+
EntityIdParam:
|
|
2204
|
+
in: path
|
|
2205
|
+
name: entity_id
|
|
2206
|
+
required: true
|
|
2207
|
+
schema: { type: string }
|
|
2208
|
+
|
|
2209
|
+
responses:
|
|
2210
|
+
BadRequest:
|
|
2211
|
+
description: Request was malformed or failed validation.
|
|
2212
|
+
content:
|
|
2213
|
+
application/problem+json:
|
|
2214
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2215
|
+
Unauthorized:
|
|
2216
|
+
description: Caller is not authenticated.
|
|
2217
|
+
content:
|
|
2218
|
+
application/problem+json:
|
|
2219
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2220
|
+
Forbidden:
|
|
2221
|
+
description: Caller is authenticated but lacks the required permission.
|
|
2222
|
+
content:
|
|
2223
|
+
application/problem+json:
|
|
2224
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2225
|
+
NotFound:
|
|
2226
|
+
description: Target resource was not found.
|
|
2227
|
+
content:
|
|
2228
|
+
application/problem+json:
|
|
2229
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2230
|
+
Conflict:
|
|
2231
|
+
description: Request conflicts with current resource state.
|
|
2232
|
+
content:
|
|
2233
|
+
application/problem+json:
|
|
2234
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2235
|
+
Unprocessable:
|
|
2236
|
+
description: Request was well-formed but semantically invalid.
|
|
2237
|
+
content:
|
|
2238
|
+
application/problem+json:
|
|
2239
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2240
|
+
PayloadTooLarge:
|
|
2241
|
+
description: Request body exceeds the server-side size cap.
|
|
2242
|
+
content:
|
|
2243
|
+
application/problem+json:
|
|
2244
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2245
|
+
ServiceUnavailable:
|
|
2246
|
+
description: Server-side dependency unavailable.
|
|
2247
|
+
content:
|
|
2248
|
+
application/problem+json:
|
|
2249
|
+
schema: { $ref: '#/components/schemas/Problem' }
|
|
2250
|
+
|
|
2251
|
+
schemas:
|
|
2252
|
+
|
|
2253
|
+
# ----------------------------------------------------------------
|
|
2254
|
+
# Error / pagination envelopes
|
|
2255
|
+
# ----------------------------------------------------------------
|
|
2256
|
+
|
|
2257
|
+
Problem:
|
|
2258
|
+
type: object
|
|
2259
|
+
description: |
|
|
2260
|
+
RFC 7807 Problem Document. Every 4xx / 5xx response in this API
|
|
2261
|
+
is served as `application/problem+json` with this shape.
|
|
2262
|
+
required: [type, title, status, detail]
|
|
2263
|
+
properties:
|
|
2264
|
+
type:
|
|
2265
|
+
type: string
|
|
2266
|
+
format: uri
|
|
2267
|
+
description: URI identifying the problem class (`about:blank` for generic statuses).
|
|
2268
|
+
default: about:blank
|
|
2269
|
+
title:
|
|
2270
|
+
type: string
|
|
2271
|
+
description: Short, human-readable summary of the problem type.
|
|
2272
|
+
status:
|
|
2273
|
+
type: integer
|
|
2274
|
+
description: HTTP status code generated by the origin server.
|
|
2275
|
+
detail:
|
|
2276
|
+
type: string
|
|
2277
|
+
description: Occurrence-specific explanation.
|
|
2278
|
+
instance:
|
|
2279
|
+
type: string
|
|
2280
|
+
format: uri-reference
|
|
2281
|
+
description: URI reference identifying this specific occurrence.
|
|
2282
|
+
|
|
2283
|
+
PageMeta:
|
|
2284
|
+
type: object
|
|
2285
|
+
description: Pagination metadata returned alongside every paged `items` array.
|
|
2286
|
+
required: [page, per_page, total]
|
|
2287
|
+
properties:
|
|
2288
|
+
page: { type: integer, minimum: 1 }
|
|
2289
|
+
per_page: { type: integer, minimum: 1 }
|
|
2290
|
+
total:
|
|
2291
|
+
type: integer
|
|
2292
|
+
minimum: 0
|
|
2293
|
+
description: Total number of items across all pages (when known).
|
|
2294
|
+
next_cursor:
|
|
2295
|
+
type: string
|
|
2296
|
+
nullable: true
|
|
2297
|
+
description: Opaque cursor for the next page (when cursor-based).
|
|
2298
|
+
|
|
2299
|
+
# ----------------------------------------------------------------
|
|
2300
|
+
# auth / me
|
|
2301
|
+
# ----------------------------------------------------------------
|
|
2302
|
+
|
|
2303
|
+
LogoutRequest:
|
|
2304
|
+
type: object
|
|
2305
|
+
required: [session_id]
|
|
2306
|
+
properties:
|
|
2307
|
+
session_id: { type: string }
|
|
2308
|
+
LogoutResponse:
|
|
2309
|
+
type: object
|
|
2310
|
+
required: [session_id, status]
|
|
2311
|
+
properties:
|
|
2312
|
+
session_id: { type: string }
|
|
2313
|
+
status:
|
|
2314
|
+
type: string
|
|
2315
|
+
enum: [closed]
|
|
2316
|
+
|
|
2317
|
+
MePermissions:
|
|
2318
|
+
type: object
|
|
2319
|
+
required: [user_id, permissions]
|
|
2320
|
+
properties:
|
|
2321
|
+
user_id: { type: string, format: uuid }
|
|
2322
|
+
permissions:
|
|
2323
|
+
type: array
|
|
2324
|
+
items: { type: string }
|
|
2325
|
+
role_bindings:
|
|
2326
|
+
type: array
|
|
2327
|
+
items: { $ref: '#/components/schemas/RbacRoleBinding' }
|
|
2328
|
+
|
|
2329
|
+
# ----------------------------------------------------------------
|
|
2330
|
+
# users / rbac
|
|
2331
|
+
# ----------------------------------------------------------------
|
|
2332
|
+
|
|
2333
|
+
User:
|
|
2334
|
+
type: object
|
|
2335
|
+
required: [user_id, username, status, created_at]
|
|
2336
|
+
properties:
|
|
2337
|
+
user_id: { type: string, format: uuid }
|
|
2338
|
+
username: { type: string }
|
|
2339
|
+
display_name: { type: string }
|
|
2340
|
+
email: { type: string, format: email }
|
|
2341
|
+
status:
|
|
2342
|
+
type: string
|
|
2343
|
+
enum: [active, suspended, deleted]
|
|
2344
|
+
created_at: { type: string, format: date-time }
|
|
2345
|
+
updated_at: { type: string, format: date-time }
|
|
2346
|
+
UserCreateRequest:
|
|
2347
|
+
type: object
|
|
2348
|
+
required: [username]
|
|
2349
|
+
properties:
|
|
2350
|
+
username: { type: string }
|
|
2351
|
+
display_name: { type: string }
|
|
2352
|
+
email: { type: string, format: email }
|
|
2353
|
+
initial_password:
|
|
2354
|
+
type: string
|
|
2355
|
+
description: Optional initial password for local-auth users.
|
|
2356
|
+
|
|
2357
|
+
RbacPermission:
|
|
2358
|
+
type: object
|
|
2359
|
+
required: [name]
|
|
2360
|
+
properties:
|
|
2361
|
+
name: { type: string }
|
|
2362
|
+
description: { type: string }
|
|
2363
|
+
RbacRole:
|
|
2364
|
+
type: object
|
|
2365
|
+
required: [name]
|
|
2366
|
+
properties:
|
|
2367
|
+
name: { type: string }
|
|
2368
|
+
description: { type: string }
|
|
2369
|
+
permissions:
|
|
2370
|
+
type: array
|
|
2371
|
+
items: { type: string }
|
|
2372
|
+
RbacRoleBinding:
|
|
2373
|
+
type: object
|
|
2374
|
+
required: [binding_id, user_id, role_name]
|
|
2375
|
+
properties:
|
|
2376
|
+
binding_id: { type: string, format: uuid }
|
|
2377
|
+
user_id: { type: string, format: uuid }
|
|
2378
|
+
role_name: { type: string }
|
|
2379
|
+
project_id:
|
|
2380
|
+
type: string
|
|
2381
|
+
format: uuid
|
|
2382
|
+
nullable: true
|
|
2383
|
+
description: Project scope of the binding; `null` for site-wide bindings.
|
|
2384
|
+
created_at: { type: string, format: date-time }
|
|
2385
|
+
RbacRoleBindingCreateRequest:
|
|
2386
|
+
type: object
|
|
2387
|
+
required: [user_id, role_name]
|
|
2388
|
+
properties:
|
|
2389
|
+
user_id: { type: string, format: uuid }
|
|
2390
|
+
role_name: { type: string }
|
|
2391
|
+
project_id:
|
|
2392
|
+
type: string
|
|
2393
|
+
format: uuid
|
|
2394
|
+
nullable: true
|
|
2395
|
+
RbacCheckRequest:
|
|
2396
|
+
type: object
|
|
2397
|
+
required: [permission]
|
|
2398
|
+
properties:
|
|
2399
|
+
permission: { type: string }
|
|
2400
|
+
project_id:
|
|
2401
|
+
type: string
|
|
2402
|
+
format: uuid
|
|
2403
|
+
nullable: true
|
|
2404
|
+
user_id:
|
|
2405
|
+
type: string
|
|
2406
|
+
format: uuid
|
|
2407
|
+
description: When omitted, the check runs against the calling actor.
|
|
2408
|
+
RbacCheckResponse:
|
|
2409
|
+
type: object
|
|
2410
|
+
required: [allowed, permission]
|
|
2411
|
+
properties:
|
|
2412
|
+
allowed: { type: boolean }
|
|
2413
|
+
permission: { type: string }
|
|
2414
|
+
project_id:
|
|
2415
|
+
type: string
|
|
2416
|
+
format: uuid
|
|
2417
|
+
nullable: true
|
|
2418
|
+
reason: { type: string }
|
|
2419
|
+
|
|
2420
|
+
# ----------------------------------------------------------------
|
|
2421
|
+
# audit
|
|
2422
|
+
# ----------------------------------------------------------------
|
|
2423
|
+
|
|
2424
|
+
AuditRecord:
|
|
2425
|
+
type: object
|
|
2426
|
+
required: [sequence_number, action, recorded_at]
|
|
2427
|
+
properties:
|
|
2428
|
+
sequence_number: { type: integer, minimum: 0 }
|
|
2429
|
+
action: { type: string, description: 'Action key (e.g. `project.created`).' }
|
|
2430
|
+
actor_id:
|
|
2431
|
+
type: string
|
|
2432
|
+
format: uuid
|
|
2433
|
+
nullable: true
|
|
2434
|
+
target_kind: { type: string }
|
|
2435
|
+
target_id: { type: string }
|
|
2436
|
+
recorded_at: { type: string, format: date-time }
|
|
2437
|
+
payload:
|
|
2438
|
+
type: object
|
|
2439
|
+
additionalProperties: true
|
|
2440
|
+
description: Action-specific payload bag.
|
|
2441
|
+
|
|
2442
|
+
# ----------------------------------------------------------------
|
|
2443
|
+
# secrets
|
|
2444
|
+
# ----------------------------------------------------------------
|
|
2445
|
+
|
|
2446
|
+
SecretRef:
|
|
2447
|
+
type: object
|
|
2448
|
+
required: [secret_ref, backend, created_at]
|
|
2449
|
+
properties:
|
|
2450
|
+
secret_ref:
|
|
2451
|
+
type: string
|
|
2452
|
+
description: Backend-qualified secret path (e.g. `vault/myteam/api-token`).
|
|
2453
|
+
backend: { type: string }
|
|
2454
|
+
description: { type: string }
|
|
2455
|
+
last_rotated_at:
|
|
2456
|
+
type: string
|
|
2457
|
+
format: date-time
|
|
2458
|
+
nullable: true
|
|
2459
|
+
created_at: { type: string, format: date-time }
|
|
2460
|
+
|
|
2461
|
+
# ----------------------------------------------------------------
|
|
2462
|
+
# variables
|
|
2463
|
+
# ----------------------------------------------------------------
|
|
2464
|
+
|
|
2465
|
+
Variable:
|
|
2466
|
+
type: object
|
|
2467
|
+
required: [name, scope, scope_id, value_type, value]
|
|
2468
|
+
properties:
|
|
2469
|
+
name: { type: string }
|
|
2470
|
+
scope:
|
|
2471
|
+
type: string
|
|
2472
|
+
enum: [global, project, pod, run]
|
|
2473
|
+
scope_id: { type: string }
|
|
2474
|
+
value_type:
|
|
2475
|
+
type: string
|
|
2476
|
+
enum: [string, integer, float, boolean, json, secret_ref]
|
|
2477
|
+
value: {}
|
|
2478
|
+
description: { type: string }
|
|
2479
|
+
sensitive: { type: boolean, default: false }
|
|
2480
|
+
updated_at: { type: string, format: date-time }
|
|
2481
|
+
VariableUpsertRequest:
|
|
2482
|
+
type: object
|
|
2483
|
+
required: [value_type, value]
|
|
2484
|
+
properties:
|
|
2485
|
+
value_type:
|
|
2486
|
+
type: string
|
|
2487
|
+
enum: [string, integer, float, boolean, json, secret_ref]
|
|
2488
|
+
value: {}
|
|
2489
|
+
description: { type: string }
|
|
2490
|
+
sensitive: { type: boolean, default: false }
|
|
2491
|
+
VariableEffectiveSet:
|
|
2492
|
+
type: object
|
|
2493
|
+
required: [items]
|
|
2494
|
+
properties:
|
|
2495
|
+
items:
|
|
2496
|
+
type: array
|
|
2497
|
+
items: { $ref: '#/components/schemas/VariableEffectiveEntry' }
|
|
2498
|
+
VariableEffectiveEntry:
|
|
2499
|
+
type: object
|
|
2500
|
+
required: [name, value_type, value, resolved_from]
|
|
2501
|
+
properties:
|
|
2502
|
+
name: { type: string }
|
|
2503
|
+
value_type:
|
|
2504
|
+
type: string
|
|
2505
|
+
enum: [string, integer, float, boolean, json, secret_ref]
|
|
2506
|
+
value: {}
|
|
2507
|
+
resolved_from:
|
|
2508
|
+
type: string
|
|
2509
|
+
enum: [global, project, pod, run]
|
|
2510
|
+
description: Scope that won inheritance for this variable.
|
|
2511
|
+
|
|
2512
|
+
# ----------------------------------------------------------------
|
|
2513
|
+
# flags
|
|
2514
|
+
# ----------------------------------------------------------------
|
|
2515
|
+
|
|
2516
|
+
FeatureFlag:
|
|
2517
|
+
type: object
|
|
2518
|
+
required: [flag_key, enabled, created_at]
|
|
2519
|
+
properties:
|
|
2520
|
+
flag_key: { type: string }
|
|
2521
|
+
description: { type: string }
|
|
2522
|
+
enabled: { type: boolean }
|
|
2523
|
+
rollout_percent:
|
|
2524
|
+
type: integer
|
|
2525
|
+
minimum: 0
|
|
2526
|
+
maximum: 100
|
|
2527
|
+
retired: { type: boolean, default: false }
|
|
2528
|
+
created_at: { type: string, format: date-time }
|
|
2529
|
+
updated_at: { type: string, format: date-time }
|
|
2530
|
+
FeatureFlagCreateRequest:
|
|
2531
|
+
type: object
|
|
2532
|
+
required: [flag_key]
|
|
2533
|
+
properties:
|
|
2534
|
+
flag_key: { type: string }
|
|
2535
|
+
description: { type: string }
|
|
2536
|
+
enabled: { type: boolean, default: false }
|
|
2537
|
+
rollout_percent: { type: integer, minimum: 0, maximum: 100 }
|
|
2538
|
+
FeatureFlagUpdateRequest:
|
|
2539
|
+
type: object
|
|
2540
|
+
properties:
|
|
2541
|
+
description: { type: string }
|
|
2542
|
+
enabled: { type: boolean }
|
|
2543
|
+
rollout_percent: { type: integer, minimum: 0, maximum: 100 }
|
|
2544
|
+
FeatureFlagBackend:
|
|
2545
|
+
type: object
|
|
2546
|
+
required: [name]
|
|
2547
|
+
properties:
|
|
2548
|
+
name: { type: string }
|
|
2549
|
+
description: { type: string }
|
|
2550
|
+
active: { type: boolean }
|
|
2551
|
+
FeatureFlagHistoryEntry:
|
|
2552
|
+
type: object
|
|
2553
|
+
required: [recorded_at, action]
|
|
2554
|
+
properties:
|
|
2555
|
+
recorded_at: { type: string, format: date-time }
|
|
2556
|
+
actor_id:
|
|
2557
|
+
type: string
|
|
2558
|
+
format: uuid
|
|
2559
|
+
nullable: true
|
|
2560
|
+
action: { type: string }
|
|
2561
|
+
before:
|
|
2562
|
+
type: object
|
|
2563
|
+
additionalProperties: true
|
|
2564
|
+
after:
|
|
2565
|
+
type: object
|
|
2566
|
+
additionalProperties: true
|
|
2567
|
+
FeatureFlagEvaluateRequest:
|
|
2568
|
+
type: object
|
|
2569
|
+
properties:
|
|
2570
|
+
context:
|
|
2571
|
+
type: object
|
|
2572
|
+
additionalProperties: true
|
|
2573
|
+
description: Evaluation context bag (user_id, project_id, attrs).
|
|
2574
|
+
FeatureFlagEvaluateResponse:
|
|
2575
|
+
type: object
|
|
2576
|
+
required: [flag_key, value]
|
|
2577
|
+
properties:
|
|
2578
|
+
flag_key: { type: string }
|
|
2579
|
+
value: { type: boolean }
|
|
2580
|
+
reason: { type: string }
|
|
2581
|
+
|
|
2582
|
+
# ----------------------------------------------------------------
|
|
2583
|
+
# tagging
|
|
2584
|
+
# ----------------------------------------------------------------
|
|
2585
|
+
|
|
2586
|
+
Tag:
|
|
2587
|
+
type: object
|
|
2588
|
+
required: [tag, entity_kind, entity_id]
|
|
2589
|
+
properties:
|
|
2590
|
+
tag: { type: string }
|
|
2591
|
+
entity_kind: { type: string }
|
|
2592
|
+
entity_id: { type: string }
|
|
2593
|
+
taxonomy: { type: string }
|
|
2594
|
+
attached_at: { type: string, format: date-time }
|
|
2595
|
+
TagAttachRequest:
|
|
2596
|
+
type: object
|
|
2597
|
+
required: [tag]
|
|
2598
|
+
properties:
|
|
2599
|
+
tag: { type: string }
|
|
2600
|
+
taxonomy: { type: string }
|
|
2601
|
+
TagTaxonomy:
|
|
2602
|
+
type: object
|
|
2603
|
+
required: [name]
|
|
2604
|
+
properties:
|
|
2605
|
+
name: { type: string }
|
|
2606
|
+
description: { type: string }
|
|
2607
|
+
|
|
2608
|
+
# ----------------------------------------------------------------
|
|
2609
|
+
# retention
|
|
2610
|
+
# ----------------------------------------------------------------
|
|
2611
|
+
|
|
2612
|
+
RetentionPolicy:
|
|
2613
|
+
type: object
|
|
2614
|
+
required: [policy_id, name, data_class, action, status, created_at]
|
|
2615
|
+
properties:
|
|
2616
|
+
policy_id: { type: string, format: uuid }
|
|
2617
|
+
name: { type: string }
|
|
2618
|
+
description: { type: string }
|
|
2619
|
+
data_class: { type: string }
|
|
2620
|
+
action:
|
|
2621
|
+
type: string
|
|
2622
|
+
enum: [archive, cold_tier, delete, custom]
|
|
2623
|
+
retention_period_days: { type: integer, minimum: 0 }
|
|
2624
|
+
status:
|
|
2625
|
+
type: string
|
|
2626
|
+
enum: [draft, published, archived]
|
|
2627
|
+
created_at: { type: string, format: date-time }
|
|
2628
|
+
updated_at: { type: string, format: date-time }
|
|
2629
|
+
RetentionPolicyCreateRequest:
|
|
2630
|
+
type: object
|
|
2631
|
+
required: [name, data_class, action]
|
|
2632
|
+
properties:
|
|
2633
|
+
name: { type: string }
|
|
2634
|
+
description: { type: string }
|
|
2635
|
+
data_class: { type: string }
|
|
2636
|
+
action:
|
|
2637
|
+
type: string
|
|
2638
|
+
enum: [archive, cold_tier, delete, custom]
|
|
2639
|
+
retention_period_days: { type: integer, minimum: 0 }
|
|
2640
|
+
|
|
2641
|
+
# ----------------------------------------------------------------
|
|
2642
|
+
# notifications
|
|
2643
|
+
# ----------------------------------------------------------------
|
|
2644
|
+
|
|
2645
|
+
NotificationChannel:
|
|
2646
|
+
type: object
|
|
2647
|
+
required: [channel_id, name, kind, status, created_at]
|
|
2648
|
+
properties:
|
|
2649
|
+
channel_id: { type: string, format: uuid }
|
|
2650
|
+
name: { type: string }
|
|
2651
|
+
kind:
|
|
2652
|
+
type: string
|
|
2653
|
+
enum: [email, slack, webhook, pagerduty]
|
|
2654
|
+
config:
|
|
2655
|
+
type: object
|
|
2656
|
+
additionalProperties: true
|
|
2657
|
+
status:
|
|
2658
|
+
type: string
|
|
2659
|
+
enum: [active, disabled, error]
|
|
2660
|
+
created_at: { type: string, format: date-time }
|
|
2661
|
+
updated_at: { type: string, format: date-time }
|
|
2662
|
+
NotificationChannelCreateRequest:
|
|
2663
|
+
type: object
|
|
2664
|
+
required: [name, kind, config]
|
|
2665
|
+
properties:
|
|
2666
|
+
name: { type: string }
|
|
2667
|
+
kind:
|
|
2668
|
+
type: string
|
|
2669
|
+
enum: [email, slack, webhook, pagerduty]
|
|
2670
|
+
config:
|
|
2671
|
+
type: object
|
|
2672
|
+
additionalProperties: true
|
|
2673
|
+
|
|
2674
|
+
# ----------------------------------------------------------------
|
|
2675
|
+
# project_templates
|
|
2676
|
+
# ----------------------------------------------------------------
|
|
2677
|
+
|
|
2678
|
+
ProjectTemplate:
|
|
2679
|
+
type: object
|
|
2680
|
+
required: [template_id, name]
|
|
2681
|
+
properties:
|
|
2682
|
+
template_id: { type: string }
|
|
2683
|
+
name: { type: string }
|
|
2684
|
+
description: { type: string }
|
|
2685
|
+
version: { type: string }
|
|
2686
|
+
provided_by: { type: string }
|
|
2687
|
+
|
|
2688
|
+
# ----------------------------------------------------------------
|
|
2689
|
+
# projects
|
|
2690
|
+
# ----------------------------------------------------------------
|
|
2691
|
+
|
|
2692
|
+
Project:
|
|
2693
|
+
type: object
|
|
2694
|
+
required: [project_id, slug, display_name, status, created_at]
|
|
2695
|
+
properties:
|
|
2696
|
+
project_id: { type: string, format: uuid }
|
|
2697
|
+
slug: { type: string }
|
|
2698
|
+
display_name: { type: string }
|
|
2699
|
+
description: { type: string }
|
|
2700
|
+
owning_group_id:
|
|
2701
|
+
type: string
|
|
2702
|
+
format: uuid
|
|
2703
|
+
nullable: true
|
|
2704
|
+
template_id:
|
|
2705
|
+
type: string
|
|
2706
|
+
nullable: true
|
|
2707
|
+
template_version:
|
|
2708
|
+
type: string
|
|
2709
|
+
nullable: true
|
|
2710
|
+
status:
|
|
2711
|
+
type: string
|
|
2712
|
+
enum: [active, archived, deleted]
|
|
2713
|
+
extension_fields:
|
|
2714
|
+
type: object
|
|
2715
|
+
additionalProperties: true
|
|
2716
|
+
created_at: { type: string, format: date-time }
|
|
2717
|
+
updated_at: { type: string, format: date-time }
|
|
2718
|
+
ProjectCreateRequest:
|
|
2719
|
+
type: object
|
|
2720
|
+
required: [slug, display_name]
|
|
2721
|
+
properties:
|
|
2722
|
+
slug: { type: string, pattern: '^[a-z0-9][a-z0-9-]*$' }
|
|
2723
|
+
display_name: { type: string }
|
|
2724
|
+
description: { type: string }
|
|
2725
|
+
owning_group_id:
|
|
2726
|
+
type: string
|
|
2727
|
+
format: uuid
|
|
2728
|
+
nullable: true
|
|
2729
|
+
template_id:
|
|
2730
|
+
type: string
|
|
2731
|
+
nullable: true
|
|
2732
|
+
template_version:
|
|
2733
|
+
type: string
|
|
2734
|
+
nullable: true
|
|
2735
|
+
ProjectPatchRequest:
|
|
2736
|
+
type: object
|
|
2737
|
+
properties:
|
|
2738
|
+
display_name: { type: string }
|
|
2739
|
+
description: { type: string }
|
|
2740
|
+
extension_fields:
|
|
2741
|
+
type: object
|
|
2742
|
+
additionalProperties: true
|
|
2743
|
+
ProjectMember:
|
|
2744
|
+
type: object
|
|
2745
|
+
required: [user_id, role_bindings]
|
|
2746
|
+
properties:
|
|
2747
|
+
user_id: { type: string, format: uuid }
|
|
2748
|
+
username: { type: string }
|
|
2749
|
+
display_name: { type: string }
|
|
2750
|
+
role_bindings:
|
|
2751
|
+
type: array
|
|
2752
|
+
items: { $ref: '#/components/schemas/RbacRoleBinding' }
|
|
2753
|
+
ProjectMemberAddRequest:
|
|
2754
|
+
type: object
|
|
2755
|
+
required: [user_id, role_name]
|
|
2756
|
+
properties:
|
|
2757
|
+
user_id: { type: string, format: uuid }
|
|
2758
|
+
role_name: { type: string }
|
|
2759
|
+
|
|
2760
|
+
# ----------------------------------------------------------------
|
|
2761
|
+
# clients / enrollment / agents
|
|
2762
|
+
# ----------------------------------------------------------------
|
|
2763
|
+
|
|
2764
|
+
Client:
|
|
2765
|
+
type: object
|
|
2766
|
+
required: [client_id, status, created_at]
|
|
2767
|
+
properties:
|
|
2768
|
+
client_id: { type: string, format: uuid }
|
|
2769
|
+
display_name: { type: string }
|
|
2770
|
+
status:
|
|
2771
|
+
type: string
|
|
2772
|
+
enum: [pending, active, revoked]
|
|
2773
|
+
attributes:
|
|
2774
|
+
type: object
|
|
2775
|
+
additionalProperties: true
|
|
2776
|
+
last_seen_at:
|
|
2777
|
+
type: string
|
|
2778
|
+
format: date-time
|
|
2779
|
+
nullable: true
|
|
2780
|
+
agent_version: { type: string }
|
|
2781
|
+
created_at: { type: string, format: date-time }
|
|
2782
|
+
updated_at: { type: string, format: date-time }
|
|
2783
|
+
ClientRevokeRequest:
|
|
2784
|
+
type: object
|
|
2785
|
+
properties:
|
|
2786
|
+
reason: { type: string }
|
|
2787
|
+
ClientAttributesUpdateRequest:
|
|
2788
|
+
type: object
|
|
2789
|
+
required: [attributes]
|
|
2790
|
+
properties:
|
|
2791
|
+
attributes:
|
|
2792
|
+
type: object
|
|
2793
|
+
additionalProperties: true
|
|
2794
|
+
ClientInventory:
|
|
2795
|
+
type: object
|
|
2796
|
+
required: [items, generated_at]
|
|
2797
|
+
properties:
|
|
2798
|
+
items:
|
|
2799
|
+
type: array
|
|
2800
|
+
items: { $ref: '#/components/schemas/Client' }
|
|
2801
|
+
generated_at: { type: string, format: date-time }
|
|
2802
|
+
ClientCapabilities:
|
|
2803
|
+
type: object
|
|
2804
|
+
required: [client_id, capabilities, advertised_at]
|
|
2805
|
+
properties:
|
|
2806
|
+
client_id: { type: string, format: uuid }
|
|
2807
|
+
capabilities:
|
|
2808
|
+
type: array
|
|
2809
|
+
items: { $ref: '#/components/schemas/CapabilityEntry' }
|
|
2810
|
+
advertised_at: { type: string, format: date-time }
|
|
2811
|
+
CapabilityEntry:
|
|
2812
|
+
type: object
|
|
2813
|
+
required: [kind, name]
|
|
2814
|
+
properties:
|
|
2815
|
+
kind:
|
|
2816
|
+
type: string
|
|
2817
|
+
description: Capability kind name (resolves a `CapabilityKindExtension`).
|
|
2818
|
+
name: { type: string }
|
|
2819
|
+
version: { type: string }
|
|
2820
|
+
attributes:
|
|
2821
|
+
type: object
|
|
2822
|
+
additionalProperties: true
|
|
2823
|
+
|
|
2824
|
+
EnrollmentToken:
|
|
2825
|
+
type: object
|
|
2826
|
+
required: [token_id, created_at]
|
|
2827
|
+
properties:
|
|
2828
|
+
token_id: { type: string, format: uuid }
|
|
2829
|
+
description: { type: string }
|
|
2830
|
+
expires_at:
|
|
2831
|
+
type: string
|
|
2832
|
+
format: date-time
|
|
2833
|
+
nullable: true
|
|
2834
|
+
max_redemptions: { type: integer, minimum: 1 }
|
|
2835
|
+
redeemed_count: { type: integer, minimum: 0 }
|
|
2836
|
+
created_at: { type: string, format: date-time }
|
|
2837
|
+
revoked_at:
|
|
2838
|
+
type: string
|
|
2839
|
+
format: date-time
|
|
2840
|
+
nullable: true
|
|
2841
|
+
EnrollmentTokenCreateRequest:
|
|
2842
|
+
type: object
|
|
2843
|
+
properties:
|
|
2844
|
+
description: { type: string }
|
|
2845
|
+
ttl_seconds: { type: integer, minimum: 60 }
|
|
2846
|
+
max_redemptions: { type: integer, minimum: 1, default: 1 }
|
|
2847
|
+
EnrollmentTokenWithSecret:
|
|
2848
|
+
allOf:
|
|
2849
|
+
- $ref: '#/components/schemas/EnrollmentToken'
|
|
2850
|
+
- type: object
|
|
2851
|
+
required: [bootstrap_secret]
|
|
2852
|
+
properties:
|
|
2853
|
+
bootstrap_secret:
|
|
2854
|
+
type: string
|
|
2855
|
+
description: One-time secret returned only at issuance.
|
|
2856
|
+
EnrollmentRedeemRequest:
|
|
2857
|
+
type: object
|
|
2858
|
+
required: [bootstrap_secret, csr_pem]
|
|
2859
|
+
properties:
|
|
2860
|
+
bootstrap_secret: { type: string }
|
|
2861
|
+
csr_pem:
|
|
2862
|
+
type: string
|
|
2863
|
+
description: PEM-encoded PKCS#10 certificate signing request.
|
|
2864
|
+
display_name: { type: string }
|
|
2865
|
+
attributes:
|
|
2866
|
+
type: object
|
|
2867
|
+
additionalProperties: true
|
|
2868
|
+
EnrollmentRedeemResponse:
|
|
2869
|
+
type: object
|
|
2870
|
+
required: [client_id, client_certificate_pem, ca_chain_pem]
|
|
2871
|
+
properties:
|
|
2872
|
+
client_id: { type: string, format: uuid }
|
|
2873
|
+
client_certificate_pem: { type: string }
|
|
2874
|
+
ca_chain_pem: { type: string }
|
|
2875
|
+
not_after: { type: string, format: date-time }
|
|
2876
|
+
envelope_signing_keys:
|
|
2877
|
+
description: |
|
|
2878
|
+
The JWKS-style Ed25519 public-key set the service signs job
|
|
2879
|
+
envelopes with, bundled here so a freshly-enrolled client
|
|
2880
|
+
can verify signed envelopes without an extra call to
|
|
2881
|
+
`GET /envelope-signing-keys`. Empty when no signing key is
|
|
2882
|
+
provisioned; the client should refresh via the dedicated
|
|
2883
|
+
endpoint in that case.
|
|
2884
|
+
type: array
|
|
2885
|
+
items: { $ref: '#/components/schemas/SigningKey' }
|
|
2886
|
+
|
|
2887
|
+
SigningKey:
|
|
2888
|
+
type: object
|
|
2889
|
+
description: |
|
|
2890
|
+
A single Ed25519 public key in the envelope-signing key set.
|
|
2891
|
+
Only the public half is ever serialized — the private signing
|
|
2892
|
+
key never leaves the service process.
|
|
2893
|
+
required: [key_id, algorithm, public_key_pem, status]
|
|
2894
|
+
properties:
|
|
2895
|
+
key_id:
|
|
2896
|
+
type: string
|
|
2897
|
+
description: JWKS-style identifier referenced by each signed envelope.
|
|
2898
|
+
algorithm:
|
|
2899
|
+
type: string
|
|
2900
|
+
enum: [Ed25519]
|
|
2901
|
+
public_key_pem:
|
|
2902
|
+
type: string
|
|
2903
|
+
description: PEM-encoded SubjectPublicKeyInfo public key.
|
|
2904
|
+
status:
|
|
2905
|
+
type: string
|
|
2906
|
+
description: |
|
|
2907
|
+
`active` — the key currently signing envelopes.
|
|
2908
|
+
`retiring` — a previous-generation key kept verifiable
|
|
2909
|
+
through the rotation grace window.
|
|
2910
|
+
enum: [active, retiring]
|
|
2911
|
+
|
|
2912
|
+
SigningKeySet:
|
|
2913
|
+
type: object
|
|
2914
|
+
required: [keys]
|
|
2915
|
+
properties:
|
|
2916
|
+
keys:
|
|
2917
|
+
type: array
|
|
2918
|
+
items: { $ref: '#/components/schemas/SigningKey' }
|
|
2919
|
+
|
|
2920
|
+
AgentRollout:
|
|
2921
|
+
type: object
|
|
2922
|
+
required: [rollout_id, status, created_at]
|
|
2923
|
+
properties:
|
|
2924
|
+
rollout_id: { type: string, format: uuid }
|
|
2925
|
+
agent_version: { type: string }
|
|
2926
|
+
targeting:
|
|
2927
|
+
type: object
|
|
2928
|
+
additionalProperties: true
|
|
2929
|
+
status:
|
|
2930
|
+
type: string
|
|
2931
|
+
enum: [draft, scheduled, running, completed, halted]
|
|
2932
|
+
created_at: { type: string, format: date-time }
|
|
2933
|
+
started_at:
|
|
2934
|
+
type: string
|
|
2935
|
+
format: date-time
|
|
2936
|
+
nullable: true
|
|
2937
|
+
completed_at:
|
|
2938
|
+
type: string
|
|
2939
|
+
format: date-time
|
|
2940
|
+
nullable: true
|
|
2941
|
+
AgentRolloutCreateRequest:
|
|
2942
|
+
type: object
|
|
2943
|
+
required: [agent_version]
|
|
2944
|
+
properties:
|
|
2945
|
+
agent_version: { type: string }
|
|
2946
|
+
targeting:
|
|
2947
|
+
type: object
|
|
2948
|
+
additionalProperties: true
|
|
2949
|
+
|
|
2950
|
+
# ----------------------------------------------------------------
|
|
2951
|
+
# pods
|
|
2952
|
+
# ----------------------------------------------------------------
|
|
2953
|
+
|
|
2954
|
+
PodDefinition:
|
|
2955
|
+
type: object
|
|
2956
|
+
required: [pod_id, project_id, name, created_at]
|
|
2957
|
+
properties:
|
|
2958
|
+
pod_id: { type: string, format: uuid }
|
|
2959
|
+
project_id: { type: string, format: uuid }
|
|
2960
|
+
name: { type: string }
|
|
2961
|
+
description: { type: string }
|
|
2962
|
+
members:
|
|
2963
|
+
type: array
|
|
2964
|
+
items: { $ref: '#/components/schemas/PodMember' }
|
|
2965
|
+
attributes:
|
|
2966
|
+
type: object
|
|
2967
|
+
additionalProperties: true
|
|
2968
|
+
created_at: { type: string, format: date-time }
|
|
2969
|
+
updated_at: { type: string, format: date-time }
|
|
2970
|
+
PodDefinitionCreateRequest:
|
|
2971
|
+
type: object
|
|
2972
|
+
required: [name]
|
|
2973
|
+
properties:
|
|
2974
|
+
name: { type: string }
|
|
2975
|
+
description: { type: string }
|
|
2976
|
+
members:
|
|
2977
|
+
type: array
|
|
2978
|
+
items: { $ref: '#/components/schemas/PodMember' }
|
|
2979
|
+
attributes:
|
|
2980
|
+
type: object
|
|
2981
|
+
additionalProperties: true
|
|
2982
|
+
PodMember:
|
|
2983
|
+
type: object
|
|
2984
|
+
required: [name, kind]
|
|
2985
|
+
properties:
|
|
2986
|
+
name: { type: string }
|
|
2987
|
+
kind:
|
|
2988
|
+
type: string
|
|
2989
|
+
description: Pod-member kind name (resolves a `PodMemberKindExtension`).
|
|
2990
|
+
attributes:
|
|
2991
|
+
type: object
|
|
2992
|
+
additionalProperties: true
|
|
2993
|
+
PodState:
|
|
2994
|
+
type: object
|
|
2995
|
+
required: [pod_id, state, updated_at]
|
|
2996
|
+
properties:
|
|
2997
|
+
pod_id: { type: string, format: uuid }
|
|
2998
|
+
state:
|
|
2999
|
+
type: string
|
|
3000
|
+
enum:
|
|
3001
|
+
- declared
|
|
3002
|
+
- provisioning
|
|
3003
|
+
- registering
|
|
3004
|
+
- ready
|
|
3005
|
+
- busy
|
|
3006
|
+
- draining
|
|
3007
|
+
- offline
|
|
3008
|
+
- error
|
|
3009
|
+
- retired
|
|
3010
|
+
updated_at: { type: string, format: date-time }
|
|
3011
|
+
last_message: { type: string }
|
|
3012
|
+
PodStateTransition:
|
|
3013
|
+
type: object
|
|
3014
|
+
required: [pod_id, from_state, to_state, occurred_at]
|
|
3015
|
+
properties:
|
|
3016
|
+
pod_id: { type: string, format: uuid }
|
|
3017
|
+
from_state: { type: string }
|
|
3018
|
+
to_state: { type: string }
|
|
3019
|
+
occurred_at: { type: string, format: date-time }
|
|
3020
|
+
actor_id:
|
|
3021
|
+
type: string
|
|
3022
|
+
format: uuid
|
|
3023
|
+
nullable: true
|
|
3024
|
+
reason: { type: string }
|
|
3025
|
+
PodRegistration:
|
|
3026
|
+
type: object
|
|
3027
|
+
required: [pod_id, client_id, registered_at]
|
|
3028
|
+
properties:
|
|
3029
|
+
pod_id: { type: string, format: uuid }
|
|
3030
|
+
client_id: { type: string, format: uuid }
|
|
3031
|
+
registered_at: { type: string, format: date-time }
|
|
3032
|
+
attributes:
|
|
3033
|
+
type: object
|
|
3034
|
+
additionalProperties: true
|
|
3035
|
+
PodTopology:
|
|
3036
|
+
type: object
|
|
3037
|
+
required: [pod_id, members, links]
|
|
3038
|
+
properties:
|
|
3039
|
+
pod_id: { type: string, format: uuid }
|
|
3040
|
+
members:
|
|
3041
|
+
type: array
|
|
3042
|
+
items: { $ref: '#/components/schemas/PodMember' }
|
|
3043
|
+
links:
|
|
3044
|
+
type: array
|
|
3045
|
+
items: { $ref: '#/components/schemas/PodTopologyLink' }
|
|
3046
|
+
PodTopologyLink:
|
|
3047
|
+
type: object
|
|
3048
|
+
required: [from_member, to_member, kind]
|
|
3049
|
+
properties:
|
|
3050
|
+
from_member: { type: string }
|
|
3051
|
+
to_member: { type: string }
|
|
3052
|
+
kind: { type: string }
|
|
3053
|
+
attributes:
|
|
3054
|
+
type: object
|
|
3055
|
+
additionalProperties: true
|
|
3056
|
+
PodTemplate:
|
|
3057
|
+
type: object
|
|
3058
|
+
required: [template_id, project_id, name]
|
|
3059
|
+
properties:
|
|
3060
|
+
template_id: { type: string }
|
|
3061
|
+
project_id: { type: string, format: uuid }
|
|
3062
|
+
name: { type: string }
|
|
3063
|
+
description: { type: string }
|
|
3064
|
+
parameters:
|
|
3065
|
+
type: object
|
|
3066
|
+
additionalProperties: true
|
|
3067
|
+
PodTemplateCreateRequest:
|
|
3068
|
+
type: object
|
|
3069
|
+
required: [template_id, name]
|
|
3070
|
+
properties:
|
|
3071
|
+
template_id: { type: string }
|
|
3072
|
+
name: { type: string }
|
|
3073
|
+
description: { type: string }
|
|
3074
|
+
parameters:
|
|
3075
|
+
type: object
|
|
3076
|
+
additionalProperties: true
|
|
3077
|
+
PodTemplateMaterializeRequest:
|
|
3078
|
+
type: object
|
|
3079
|
+
properties:
|
|
3080
|
+
name:
|
|
3081
|
+
type: string
|
|
3082
|
+
description: Optional name override for the materialized pod.
|
|
3083
|
+
parameters:
|
|
3084
|
+
type: object
|
|
3085
|
+
additionalProperties: true
|
|
3086
|
+
|
|
3087
|
+
# ----------------------------------------------------------------
|
|
3088
|
+
# plugin_api
|
|
3089
|
+
# ----------------------------------------------------------------
|
|
3090
|
+
|
|
3091
|
+
PluginApiProtocol:
|
|
3092
|
+
type: object
|
|
3093
|
+
required: [protocol_name]
|
|
3094
|
+
properties:
|
|
3095
|
+
protocol_name: { type: string }
|
|
3096
|
+
description: { type: string }
|
|
3097
|
+
schema_version: { type: string }
|
|
3098
|
+
implementations:
|
|
3099
|
+
type: array
|
|
3100
|
+
items:
|
|
3101
|
+
type: object
|
|
3102
|
+
properties:
|
|
3103
|
+
name: { type: string }
|
|
3104
|
+
precedence: { type: integer }
|
|
3105
|
+
package: { type: string }
|
|
3106
|
+
PluginApiMount:
|
|
3107
|
+
type: object
|
|
3108
|
+
required: [mount_path]
|
|
3109
|
+
properties:
|
|
3110
|
+
mount_path: { type: string }
|
|
3111
|
+
extension_name: { type: string }
|
|
3112
|
+
capabilities:
|
|
3113
|
+
type: array
|
|
3114
|
+
items: { type: string }
|
|
3115
|
+
|
|
3116
|
+
# ----------------------------------------------------------------
|
|
3117
|
+
# JobEnvelope (planned — surfaced as a referenced schema, not a top-
|
|
3118
|
+
# level operation body, because it travels as the payload of a
|
|
3119
|
+
# signed dispatch and is not POST'd as JSON by clients of the
|
|
3120
|
+
# public REST surface.)
|
|
3121
|
+
# ----------------------------------------------------------------
|
|
3122
|
+
|
|
3123
|
+
JobEnvelope:
|
|
3124
|
+
type: object
|
|
3125
|
+
description: |
|
|
3126
|
+
Canonical dispatch envelope. Matches the dataclass in
|
|
3127
|
+
`source/site/ccai/pychestrate/webservice/jobs/envelope/schema.py`.
|
|
3128
|
+
Serialized with RFC 8785 (JCS) canonical JSON; consumers MUST
|
|
3129
|
+
verify against the declared `schema_version`
|
|
3130
|
+
(current: `2026-04-24`).
|
|
3131
|
+
required:
|
|
3132
|
+
- schema_version
|
|
3133
|
+
- envelope_id
|
|
3134
|
+
- issued_at
|
|
3135
|
+
- issuer
|
|
3136
|
+
- project_id
|
|
3137
|
+
- job_id
|
|
3138
|
+
- job_name
|
|
3139
|
+
- job_type
|
|
3140
|
+
- run_id
|
|
3141
|
+
- client_id
|
|
3142
|
+
- pod_id
|
|
3143
|
+
- refs
|
|
3144
|
+
- params
|
|
3145
|
+
- scoped_secrets
|
|
3146
|
+
- results
|
|
3147
|
+
- forwarding
|
|
3148
|
+
- deadline
|
|
3149
|
+
- extension_claims
|
|
3150
|
+
properties:
|
|
3151
|
+
schema_version: { type: string }
|
|
3152
|
+
envelope_id: { type: string }
|
|
3153
|
+
issued_at: { type: string, format: date-time }
|
|
3154
|
+
issuer: { type: string }
|
|
3155
|
+
project_id: { type: string, format: uuid }
|
|
3156
|
+
job_id: { type: string, format: uuid }
|
|
3157
|
+
job_name: { type: string }
|
|
3158
|
+
job_type: { type: string }
|
|
3159
|
+
pipeline:
|
|
3160
|
+
nullable: true
|
|
3161
|
+
$ref: '#/components/schemas/JobPipelineRef'
|
|
3162
|
+
run_id: { type: string, format: uuid }
|
|
3163
|
+
client_id: { type: string, format: uuid }
|
|
3164
|
+
pod_id: { type: string, format: uuid }
|
|
3165
|
+
refs: { $ref: '#/components/schemas/JobRefs' }
|
|
3166
|
+
params:
|
|
3167
|
+
type: object
|
|
3168
|
+
additionalProperties: true
|
|
3169
|
+
scoped_secrets:
|
|
3170
|
+
type: array
|
|
3171
|
+
items: { $ref: '#/components/schemas/ScopedSecret' }
|
|
3172
|
+
results: { $ref: '#/components/schemas/JobResultsLocator' }
|
|
3173
|
+
forwarding: { $ref: '#/components/schemas/JobForwardingInfo' }
|
|
3174
|
+
deadline: { $ref: '#/components/schemas/JobDeadline' }
|
|
3175
|
+
extension_claims:
|
|
3176
|
+
type: object
|
|
3177
|
+
additionalProperties: true
|
|
3178
|
+
JobRefs:
|
|
3179
|
+
type: object
|
|
3180
|
+
required: [branch, build, flavor, release, commit_sha]
|
|
3181
|
+
properties:
|
|
3182
|
+
branch: { type: string }
|
|
3183
|
+
build: { type: string }
|
|
3184
|
+
flavor: { type: string }
|
|
3185
|
+
release: { type: string }
|
|
3186
|
+
commit_sha: { type: string }
|
|
3187
|
+
JobPipelineRef:
|
|
3188
|
+
type: object
|
|
3189
|
+
required: [pipeline_id, instance_id]
|
|
3190
|
+
properties:
|
|
3191
|
+
pipeline_id: { type: string }
|
|
3192
|
+
instance_id: { type: string }
|
|
3193
|
+
JobResultsLocator:
|
|
3194
|
+
type: object
|
|
3195
|
+
required: [backend_key_prefix, backend_driver]
|
|
3196
|
+
properties:
|
|
3197
|
+
backend_key_prefix: { type: string }
|
|
3198
|
+
backend_driver: { type: string }
|
|
3199
|
+
JobForwardingInfo:
|
|
3200
|
+
type: object
|
|
3201
|
+
required: [progress_url, summary_url, auth_method]
|
|
3202
|
+
properties:
|
|
3203
|
+
progress_url: { type: string, format: uri }
|
|
3204
|
+
summary_url: { type: string, format: uri }
|
|
3205
|
+
auth_method:
|
|
3206
|
+
type: string
|
|
3207
|
+
enum: [bearer, mtls]
|
|
3208
|
+
JobDeadline:
|
|
3209
|
+
type: object
|
|
3210
|
+
required: [soft_deadline_at, hard_deadline_at]
|
|
3211
|
+
properties:
|
|
3212
|
+
soft_deadline_at: { type: string, format: date-time }
|
|
3213
|
+
hard_deadline_at: { type: string, format: date-time }
|
|
3214
|
+
|
|
3215
|
+
PendingDispatch:
|
|
3216
|
+
type: object
|
|
3217
|
+
description: |
|
|
3218
|
+
Server response to `GET /api/v1/clients/{client_id}/dispatches:next`
|
|
3219
|
+
when a queued dispatch is available. Carries the signed
|
|
3220
|
+
`JobEnvelope` plus its detached Ed25519 signature and the
|
|
3221
|
+
ack-deadline the agent must beat. The signature is hex-encoded;
|
|
3222
|
+
agents re-verify against the canonical JSON serialization of the
|
|
3223
|
+
embedded `envelope` field.
|
|
3224
|
+
required:
|
|
3225
|
+
- dispatch_attempt_id
|
|
3226
|
+
- run_id
|
|
3227
|
+
- client_id
|
|
3228
|
+
- pod_id
|
|
3229
|
+
- envelope_id
|
|
3230
|
+
- enqueued_at
|
|
3231
|
+
- ack_deadline_at
|
|
3232
|
+
- envelope
|
|
3233
|
+
- signature
|
|
3234
|
+
- signature_key_id
|
|
3235
|
+
- signed_at
|
|
3236
|
+
- transport_name
|
|
3237
|
+
properties:
|
|
3238
|
+
dispatch_attempt_id: { type: string, format: uuid }
|
|
3239
|
+
run_id: { type: string, format: uuid }
|
|
3240
|
+
client_id: { type: string, format: uuid }
|
|
3241
|
+
pod_id: { type: string, format: uuid }
|
|
3242
|
+
envelope_id: { type: string, format: uuid }
|
|
3243
|
+
enqueued_at: { type: string, format: date-time }
|
|
3244
|
+
ack_deadline_at: { type: string, format: date-time }
|
|
3245
|
+
envelope: { $ref: '#/components/schemas/JobEnvelope' }
|
|
3246
|
+
signature:
|
|
3247
|
+
type: string
|
|
3248
|
+
description: Hex-encoded Ed25519 detached signature.
|
|
3249
|
+
signature_key_id:
|
|
3250
|
+
type: string
|
|
3251
|
+
description: JWKS key id for the signing key.
|
|
3252
|
+
signed_at: { type: string, format: date-time }
|
|
3253
|
+
transport_name:
|
|
3254
|
+
type: string
|
|
3255
|
+
description: Stable transport identifier (`http-long-poll`).
|
|
3256
|
+
ScopedSecret:
|
|
3257
|
+
type: object
|
|
3258
|
+
required: [name, value, expires_at, token_type]
|
|
3259
|
+
properties:
|
|
3260
|
+
name: { type: string }
|
|
3261
|
+
value: { type: string }
|
|
3262
|
+
expires_at: { type: string, format: date-time }
|
|
3263
|
+
token_type: { type: string }
|
|
3264
|
+
|
|
3265
|
+
# ----------------------------------------------------------------
|
|
3266
|
+
# RunState / Run
|
|
3267
|
+
# ----------------------------------------------------------------
|
|
3268
|
+
|
|
3269
|
+
RunState:
|
|
3270
|
+
type: string
|
|
3271
|
+
description: |
|
|
3272
|
+
Run lifecycle state. Mirrors
|
|
3273
|
+
`jobs.run.state.enums.RunState`.
|
|
3274
|
+
enum:
|
|
3275
|
+
- pending
|
|
3276
|
+
- dispatching
|
|
3277
|
+
- dispatched
|
|
3278
|
+
- dispatch_unconfirmed
|
|
3279
|
+
- dispatch_lost
|
|
3280
|
+
- dispatch_failed
|
|
3281
|
+
- running
|
|
3282
|
+
- progress_stalled
|
|
3283
|
+
- cancel_requested
|
|
3284
|
+
- cancelling
|
|
3285
|
+
- cancelled
|
|
3286
|
+
- completing
|
|
3287
|
+
- completed
|
|
3288
|
+
- failed
|
|
3289
|
+
- needs_investigation
|
|
3290
|
+
|
|
3291
|
+
TerminalOutcome:
|
|
3292
|
+
type: string
|
|
3293
|
+
enum:
|
|
3294
|
+
- success
|
|
3295
|
+
- failure
|
|
3296
|
+
- cancelled
|
|
3297
|
+
- dispatch_failed
|
|
3298
|
+
- dispatch_lost
|
|
3299
|
+
|
|
3300
|
+
Run:
|
|
3301
|
+
type: object
|
|
3302
|
+
required: [run_id, project_id, pod_id, job_type, state, created_at]
|
|
3303
|
+
properties:
|
|
3304
|
+
run_id: { type: string, format: uuid }
|
|
3305
|
+
project_id: { type: string, format: uuid }
|
|
3306
|
+
pod_id: { type: string, format: uuid }
|
|
3307
|
+
client_id:
|
|
3308
|
+
type: string
|
|
3309
|
+
format: uuid
|
|
3310
|
+
nullable: true
|
|
3311
|
+
job_id: { type: string, format: uuid }
|
|
3312
|
+
job_name: { type: string }
|
|
3313
|
+
job_type: { type: string }
|
|
3314
|
+
state: { $ref: '#/components/schemas/RunState' }
|
|
3315
|
+
terminal_outcome:
|
|
3316
|
+
nullable: true
|
|
3317
|
+
$ref: '#/components/schemas/TerminalOutcome'
|
|
3318
|
+
current_dispatch_attempt_id:
|
|
3319
|
+
type: string
|
|
3320
|
+
format: uuid
|
|
3321
|
+
nullable: true
|
|
3322
|
+
progress_summary:
|
|
3323
|
+
nullable: true
|
|
3324
|
+
$ref: '#/components/schemas/ProgressSummary'
|
|
3325
|
+
created_at: { type: string, format: date-time }
|
|
3326
|
+
dispatched_at:
|
|
3327
|
+
type: string
|
|
3328
|
+
format: date-time
|
|
3329
|
+
nullable: true
|
|
3330
|
+
started_at:
|
|
3331
|
+
type: string
|
|
3332
|
+
format: date-time
|
|
3333
|
+
nullable: true
|
|
3334
|
+
completed_at:
|
|
3335
|
+
type: string
|
|
3336
|
+
format: date-time
|
|
3337
|
+
nullable: true
|
|
3338
|
+
labels:
|
|
3339
|
+
type: object
|
|
3340
|
+
additionalProperties: { type: string }
|
|
3341
|
+
RunPage:
|
|
3342
|
+
type: object
|
|
3343
|
+
required: [items, meta]
|
|
3344
|
+
properties:
|
|
3345
|
+
items:
|
|
3346
|
+
type: array
|
|
3347
|
+
items: { $ref: '#/components/schemas/Run' }
|
|
3348
|
+
meta: { $ref: '#/components/schemas/PageMeta' }
|
|
3349
|
+
|
|
3350
|
+
# ----------------------------------------------------------------
|
|
3351
|
+
# SSE events
|
|
3352
|
+
# ----------------------------------------------------------------
|
|
3353
|
+
|
|
3354
|
+
RunEvent:
|
|
3355
|
+
type: object
|
|
3356
|
+
description: |
|
|
3357
|
+
One event emitted on the `/runs/{run_id}/events` SSE stream.
|
|
3358
|
+
The SSE `event:` field carries the event type tag; the SSE
|
|
3359
|
+
`data:` field carries this JSON object.
|
|
3360
|
+
required: [run_id, event_type, sequence, occurred_at]
|
|
3361
|
+
properties:
|
|
3362
|
+
run_id: { type: string, format: uuid }
|
|
3363
|
+
event_type:
|
|
3364
|
+
type: string
|
|
3365
|
+
enum: [state_changed, progress, result_manifest, heartbeat]
|
|
3366
|
+
sequence: { type: integer, minimum: 0 }
|
|
3367
|
+
occurred_at: { type: string, format: date-time }
|
|
3368
|
+
state: { $ref: '#/components/schemas/RunState' }
|
|
3369
|
+
terminal_outcome:
|
|
3370
|
+
nullable: true
|
|
3371
|
+
$ref: '#/components/schemas/TerminalOutcome'
|
|
3372
|
+
progress:
|
|
3373
|
+
nullable: true
|
|
3374
|
+
$ref: '#/components/schemas/ProgressSummary'
|
|
3375
|
+
manifest:
|
|
3376
|
+
nullable: true
|
|
3377
|
+
$ref: '#/components/schemas/ResultManifest'
|
|
3378
|
+
|
|
3379
|
+
# ----------------------------------------------------------------
|
|
3380
|
+
# dispatch / progress / results
|
|
3381
|
+
# ----------------------------------------------------------------
|
|
3382
|
+
|
|
3383
|
+
DispatchAck:
|
|
3384
|
+
type: object
|
|
3385
|
+
description: |
|
|
3386
|
+
Posted by the pod client agent to `POST /runs/{run_id}/dispatch-ack`
|
|
3387
|
+
to confirm receipt of a dispatched envelope.
|
|
3388
|
+
required: [envelope_id, dispatch_attempt_id, client_id, local_run_id, accepted_at]
|
|
3389
|
+
properties:
|
|
3390
|
+
envelope_id: { type: string }
|
|
3391
|
+
dispatch_attempt_id: { type: string, format: uuid }
|
|
3392
|
+
client_id: { type: string, format: uuid }
|
|
3393
|
+
local_run_id:
|
|
3394
|
+
type: string
|
|
3395
|
+
description: Local identifier minted by the client agent for reconciliation.
|
|
3396
|
+
accepted_at: { type: string, format: date-time }
|
|
3397
|
+
notes: { type: string }
|
|
3398
|
+
|
|
3399
|
+
ProgressSummary:
|
|
3400
|
+
type: object
|
|
3401
|
+
description: |
|
|
3402
|
+
Roll-up the client agent emits at intervals. Matches the
|
|
3403
|
+
testbase `ForwardingInfo` shape; see
|
|
3404
|
+
`ingest.progress.schema.ProgressSummary`.
|
|
3405
|
+
required: [run_id, client_id, format_tag, tests_passed, tests_failed, tests_skipped, tests_errored, recorded_at]
|
|
3406
|
+
properties:
|
|
3407
|
+
run_id: { type: string, format: uuid }
|
|
3408
|
+
client_id: { type: string, format: uuid }
|
|
3409
|
+
format_tag:
|
|
3410
|
+
type: string
|
|
3411
|
+
description: Parser tag (`testbase` for the in-tree default).
|
|
3412
|
+
tests_total:
|
|
3413
|
+
type: integer
|
|
3414
|
+
nullable: true
|
|
3415
|
+
tests_passed: { type: integer, minimum: 0 }
|
|
3416
|
+
tests_failed: { type: integer, minimum: 0 }
|
|
3417
|
+
tests_skipped: { type: integer, minimum: 0 }
|
|
3418
|
+
tests_errored: { type: integer, minimum: 0 }
|
|
3419
|
+
percent_complete:
|
|
3420
|
+
type: number
|
|
3421
|
+
minimum: 0
|
|
3422
|
+
maximum: 100
|
|
3423
|
+
nullable: true
|
|
3424
|
+
notes:
|
|
3425
|
+
type: string
|
|
3426
|
+
nullable: true
|
|
3427
|
+
recorded_at: { type: string, format: date-time }
|
|
3428
|
+
raw:
|
|
3429
|
+
type: object
|
|
3430
|
+
additionalProperties: true
|
|
3431
|
+
|
|
3432
|
+
ProgressRecord:
|
|
3433
|
+
type: object
|
|
3434
|
+
description: |
|
|
3435
|
+
One progress record from the testbase
|
|
3436
|
+
`testrun_results_stream.jsos` stream — represents one test
|
|
3437
|
+
scope completion (or preview).
|
|
3438
|
+
required: [run_id, client_id, record_index, test_name, result]
|
|
3439
|
+
properties:
|
|
3440
|
+
run_id: { type: string, format: uuid }
|
|
3441
|
+
client_id: { type: string, format: uuid }
|
|
3442
|
+
record_index: { type: integer, minimum: 0 }
|
|
3443
|
+
test_name: { type: string }
|
|
3444
|
+
result:
|
|
3445
|
+
type: string
|
|
3446
|
+
enum: [PASSED, FAILED, SKIPPED, ERRORED, UNSET]
|
|
3447
|
+
started_at:
|
|
3448
|
+
type: string
|
|
3449
|
+
format: date-time
|
|
3450
|
+
nullable: true
|
|
3451
|
+
finished_at:
|
|
3452
|
+
type: string
|
|
3453
|
+
format: date-time
|
|
3454
|
+
nullable: true
|
|
3455
|
+
duration_seconds:
|
|
3456
|
+
type: number
|
|
3457
|
+
nullable: true
|
|
3458
|
+
raw:
|
|
3459
|
+
type: object
|
|
3460
|
+
additionalProperties: true
|
|
3461
|
+
|
|
3462
|
+
ProgressFrame:
|
|
3463
|
+
type: object
|
|
3464
|
+
description: |
|
|
3465
|
+
Body of `POST /runs/{run_id}/progress`. One frame carries either
|
|
3466
|
+
a summary roll-up, or a batch of per-test records, or both.
|
|
3467
|
+
properties:
|
|
3468
|
+
summary:
|
|
3469
|
+
nullable: true
|
|
3470
|
+
$ref: '#/components/schemas/ProgressSummary'
|
|
3471
|
+
records:
|
|
3472
|
+
type: array
|
|
3473
|
+
items: { $ref: '#/components/schemas/ProgressRecord' }
|
|
3474
|
+
|
|
3475
|
+
ResultManifest:
|
|
3476
|
+
type: object
|
|
3477
|
+
description: |
|
|
3478
|
+
Body of `POST /runs/{run_id}/results/complete`. Signals that
|
|
3479
|
+
the agent has finished writing the full testbase result tree
|
|
3480
|
+
to the shared results backend and tells the server where the
|
|
3481
|
+
tree lives + how it should be classified.
|
|
3482
|
+
required:
|
|
3483
|
+
- run_id
|
|
3484
|
+
- client_id
|
|
3485
|
+
- format_tag
|
|
3486
|
+
- backend_key_prefix
|
|
3487
|
+
- completed_at
|
|
3488
|
+
- terminal_outcome
|
|
3489
|
+
properties:
|
|
3490
|
+
run_id: { type: string, format: uuid }
|
|
3491
|
+
client_id: { type: string, format: uuid }
|
|
3492
|
+
format_tag:
|
|
3493
|
+
type: string
|
|
3494
|
+
description: Parser tag (`testbase` for testbase result trees).
|
|
3495
|
+
backend_key_prefix:
|
|
3496
|
+
type: string
|
|
3497
|
+
description: Per-run prefix the agent wrote the tree under
|
|
3498
|
+
(echoes `JobResultsLocator.backend_key_prefix`).
|
|
3499
|
+
backend_driver:
|
|
3500
|
+
type: string
|
|
3501
|
+
description: Object-store driver name the agent used.
|
|
3502
|
+
completed_at: { type: string, format: date-time }
|
|
3503
|
+
terminal_outcome: { $ref: '#/components/schemas/TerminalOutcome' }
|
|
3504
|
+
summary_artifact_path:
|
|
3505
|
+
type: string
|
|
3506
|
+
description: Path (relative to `backend_key_prefix`) of
|
|
3507
|
+
`testrun_summary.json`.
|
|
3508
|
+
default: testrun_summary.json
|
|
3509
|
+
results_stream_artifact_path:
|
|
3510
|
+
type: string
|
|
3511
|
+
description: Path of `testrun_results_stream.jsos`.
|
|
3512
|
+
default: testrun_results_stream.jsos
|
|
3513
|
+
results_artifact_path:
|
|
3514
|
+
type: string
|
|
3515
|
+
description: Path of the consolidated `testrun_results.json`.
|
|
3516
|
+
default: testrun_results.json
|
|
3517
|
+
html_summary_artifact_path:
|
|
3518
|
+
type: string
|
|
3519
|
+
description: Path of `testsummary.html`.
|
|
3520
|
+
default: testsummary.html
|
|
3521
|
+
tests_passed: { type: integer, minimum: 0 }
|
|
3522
|
+
tests_failed: { type: integer, minimum: 0 }
|
|
3523
|
+
tests_skipped: { type: integer, minimum: 0 }
|
|
3524
|
+
tests_errored: { type: integer, minimum: 0 }
|
|
3525
|
+
notes: { type: string }
|
|
3526
|
+
extras:
|
|
3527
|
+
type: object
|
|
3528
|
+
additionalProperties: true
|
|
3529
|
+
|
|
3530
|
+
# ----------------------------------------------------------------
|
|
3531
|
+
# artifacts
|
|
3532
|
+
# ----------------------------------------------------------------
|
|
3533
|
+
|
|
3534
|
+
ArtifactEntry:
|
|
3535
|
+
type: object
|
|
3536
|
+
required: [path, size_bytes, content_type]
|
|
3537
|
+
properties:
|
|
3538
|
+
path:
|
|
3539
|
+
type: string
|
|
3540
|
+
description: Slash-delimited path relative to the run's results root.
|
|
3541
|
+
size_bytes: { type: integer, minimum: 0 }
|
|
3542
|
+
content_type: { type: string }
|
|
3543
|
+
artifact_type:
|
|
3544
|
+
type: string
|
|
3545
|
+
description: Logical category assigned by an `ArtifactClassifierExtension`.
|
|
3546
|
+
download_url:
|
|
3547
|
+
type: string
|
|
3548
|
+
format: uri
|
|
3549
|
+
description: Signed URL the caller can use to download the bytes.
|
|
3550
|
+
download_url_expires_at:
|
|
3551
|
+
type: string
|
|
3552
|
+
format: date-time
|
|
3553
|
+
sha256: { type: string }
|
|
3554
|
+
recorded_at: { type: string, format: date-time }
|
|
3555
|
+
ArtifactEntryPage:
|
|
3556
|
+
type: object
|
|
3557
|
+
required: [items, meta]
|
|
3558
|
+
properties:
|
|
3559
|
+
items:
|
|
3560
|
+
type: array
|
|
3561
|
+
items: { $ref: '#/components/schemas/ArtifactEntry' }
|
|
3562
|
+
meta: { $ref: '#/components/schemas/PageMeta' }
|
|
3563
|
+
|
|
3564
|
+
ArtifactPresignRequest:
|
|
3565
|
+
type: object
|
|
3566
|
+
description: |
|
|
3567
|
+
Body of `POST /runs/{run_id}/artifacts:presign`. Carries the
|
|
3568
|
+
relative artifact paths the agent discovered while walking
|
|
3569
|
+
its result tree and wants presigned upload URLs for.
|
|
3570
|
+
required: [paths]
|
|
3571
|
+
properties:
|
|
3572
|
+
paths:
|
|
3573
|
+
type: array
|
|
3574
|
+
description: |
|
|
3575
|
+
Relative artifact paths (relative to the run's result
|
|
3576
|
+
root). Must be non-empty; the server caps the count and
|
|
3577
|
+
the summed path length and rejects an over-cap batch
|
|
3578
|
+
with 422.
|
|
3579
|
+
minItems: 1
|
|
3580
|
+
maxItems: 512
|
|
3581
|
+
items:
|
|
3582
|
+
type: string
|
|
3583
|
+
description: A single slash-delimited relative path.
|
|
3584
|
+
maxLength: 1024
|
|
3585
|
+
|
|
3586
|
+
ArtifactUploadTarget:
|
|
3587
|
+
type: object
|
|
3588
|
+
description: |
|
|
3589
|
+
One minted presigned upload target. The agent PUTs the
|
|
3590
|
+
artifact's bytes to `put_url`, sending `headers` verbatim.
|
|
3591
|
+
required: [path, put_url, expires_at, headers]
|
|
3592
|
+
properties:
|
|
3593
|
+
path:
|
|
3594
|
+
type: string
|
|
3595
|
+
description: The relative artifact path this target covers.
|
|
3596
|
+
put_url:
|
|
3597
|
+
type: string
|
|
3598
|
+
description: |
|
|
3599
|
+
URL the agent PUTs the artifact bytes to. For the
|
|
3600
|
+
local-filesystem fallback this is a path-absolute URL on
|
|
3601
|
+
this service carrying the upload signature in its query
|
|
3602
|
+
string; for an S3-backed deploy it is an absolute
|
|
3603
|
+
object-store URL.
|
|
3604
|
+
expires_at:
|
|
3605
|
+
type: string
|
|
3606
|
+
format: date-time
|
|
3607
|
+
description: UTC timestamp after which the URL is rejected.
|
|
3608
|
+
headers:
|
|
3609
|
+
type: object
|
|
3610
|
+
additionalProperties: { type: string }
|
|
3611
|
+
description: |
|
|
3612
|
+
Headers the agent must send verbatim on the PUT. Empty
|
|
3613
|
+
for the local-filesystem fallback (the signature travels
|
|
3614
|
+
in the URL query string).
|
|
3615
|
+
|
|
3616
|
+
ArtifactPresignResponse:
|
|
3617
|
+
type: object
|
|
3618
|
+
description: |
|
|
3619
|
+
Body of a successful `POST /runs/{run_id}/artifacts:presign`.
|
|
3620
|
+
Carries one upload target per requested path, in request
|
|
3621
|
+
order.
|
|
3622
|
+
required: [run_id, uploads]
|
|
3623
|
+
properties:
|
|
3624
|
+
run_id: { type: string, format: uuid }
|
|
3625
|
+
uploads:
|
|
3626
|
+
type: array
|
|
3627
|
+
items: { $ref: '#/components/schemas/ArtifactUploadTarget' }
|
|
3628
|
+
|
|
3629
|
+
ArtifactLocalUploadResult:
|
|
3630
|
+
type: object
|
|
3631
|
+
description: |
|
|
3632
|
+
Body of a successful
|
|
3633
|
+
`PUT /runs/{run_id}/artifacts/_local-upload`.
|
|
3634
|
+
required: [run_id, path, bytes_written, stored_at]
|
|
3635
|
+
properties:
|
|
3636
|
+
run_id: { type: string, format: uuid }
|
|
3637
|
+
path:
|
|
3638
|
+
type: string
|
|
3639
|
+
description: The relative artifact path the bytes were stored under.
|
|
3640
|
+
bytes_written: { type: integer, minimum: 0 }
|
|
3641
|
+
stored_at:
|
|
3642
|
+
type: string
|
|
3643
|
+
description: Server-side absolute path the bytes landed at.
|
|
3644
|
+
|
|
3645
|
+
# ----------------------------------------------------------------
|
|
3646
|
+
# test_runs
|
|
3647
|
+
# ----------------------------------------------------------------
|
|
3648
|
+
|
|
3649
|
+
TestRunCreateRequest:
|
|
3650
|
+
type: object
|
|
3651
|
+
description: |
|
|
3652
|
+
Body of `POST /projects/{project_id}/test-runs`. Either
|
|
3653
|
+
`catalog_entry_id` or `(importer_tag, entry_key)` MUST be
|
|
3654
|
+
provided so the server can resolve the catalog row.
|
|
3655
|
+
required: [pod_id]
|
|
3656
|
+
properties:
|
|
3657
|
+
catalog_entry_id:
|
|
3658
|
+
type: string
|
|
3659
|
+
format: uuid
|
|
3660
|
+
importer_tag: { type: string }
|
|
3661
|
+
entry_key: { type: string }
|
|
3662
|
+
pod_id: { type: string, format: uuid }
|
|
3663
|
+
params:
|
|
3664
|
+
type: object
|
|
3665
|
+
additionalProperties: true
|
|
3666
|
+
labels:
|
|
3667
|
+
type: object
|
|
3668
|
+
additionalProperties: { type: string }
|
|
3669
|
+
soft_deadline_seconds: { type: integer, minimum: 1 }
|
|
3670
|
+
hard_deadline_seconds: { type: integer, minimum: 1 }
|
|
3671
|
+
|
|
3672
|
+
# ----------------------------------------------------------------
|
|
3673
|
+
# test_catalog
|
|
3674
|
+
# ----------------------------------------------------------------
|
|
3675
|
+
|
|
3676
|
+
TestCatalogEntry:
|
|
3677
|
+
type: object
|
|
3678
|
+
description: |
|
|
3679
|
+
Matches the SQLAlchemy model in
|
|
3680
|
+
`orchestration.test.catalog.models.TestCatalogEntry`.
|
|
3681
|
+
required:
|
|
3682
|
+
- entry_id
|
|
3683
|
+
- project_id
|
|
3684
|
+
- importer_tag
|
|
3685
|
+
- entry_key
|
|
3686
|
+
- display_name
|
|
3687
|
+
- canonical_digest
|
|
3688
|
+
- retired
|
|
3689
|
+
- created_at
|
|
3690
|
+
properties:
|
|
3691
|
+
entry_id: { type: string, format: uuid }
|
|
3692
|
+
project_id: { type: string, format: uuid }
|
|
3693
|
+
importer_tag: { type: string }
|
|
3694
|
+
entry_key: { type: string }
|
|
3695
|
+
display_name: { type: string }
|
|
3696
|
+
description: { type: string }
|
|
3697
|
+
required_capability_tags:
|
|
3698
|
+
type: array
|
|
3699
|
+
items: { type: string }
|
|
3700
|
+
param_schema:
|
|
3701
|
+
type: object
|
|
3702
|
+
additionalProperties: true
|
|
3703
|
+
raw_metadata:
|
|
3704
|
+
type: object
|
|
3705
|
+
additionalProperties: true
|
|
3706
|
+
canonical_digest: { type: string }
|
|
3707
|
+
retired: { type: boolean }
|
|
3708
|
+
created_at: { type: string, format: date-time }
|
|
3709
|
+
updated_at: { type: string, format: date-time }
|
|
3710
|
+
TestCatalogEntryPage:
|
|
3711
|
+
type: object
|
|
3712
|
+
required: [items, meta]
|
|
3713
|
+
properties:
|
|
3714
|
+
items:
|
|
3715
|
+
type: array
|
|
3716
|
+
items: { $ref: '#/components/schemas/TestCatalogEntry' }
|
|
3717
|
+
meta: { $ref: '#/components/schemas/PageMeta' }
|
|
3718
|
+
TestCatalogEntryUpsertRequest:
|
|
3719
|
+
type: object
|
|
3720
|
+
required: [importer_tag, entry_key, display_name]
|
|
3721
|
+
properties:
|
|
3722
|
+
importer_tag: { type: string }
|
|
3723
|
+
entry_key: { type: string }
|
|
3724
|
+
display_name: { type: string }
|
|
3725
|
+
description: { type: string }
|
|
3726
|
+
required_capability_tags:
|
|
3727
|
+
type: array
|
|
3728
|
+
items: { type: string }
|
|
3729
|
+
param_schema:
|
|
3730
|
+
type: object
|
|
3731
|
+
additionalProperties: true
|
|
3732
|
+
raw_metadata:
|
|
3733
|
+
type: object
|
|
3734
|
+
additionalProperties: true
|
|
3735
|
+
retired: { type: boolean, default: false }
|
|
3736
|
+
TestCatalogImportRequest:
|
|
3737
|
+
type: object
|
|
3738
|
+
required: [importer_tag]
|
|
3739
|
+
properties:
|
|
3740
|
+
importer_tag:
|
|
3741
|
+
type: string
|
|
3742
|
+
description: Importer factory tag (e.g. `testbase-project`).
|
|
3743
|
+
source:
|
|
3744
|
+
type: object
|
|
3745
|
+
additionalProperties: true
|
|
3746
|
+
description: Importer-specific source descriptor (repo URL, branch, path).
|
|
3747
|
+
TestCatalogImportSummary:
|
|
3748
|
+
type: object
|
|
3749
|
+
required: [importer_tag, started_at]
|
|
3750
|
+
properties:
|
|
3751
|
+
importer_tag: { type: string }
|
|
3752
|
+
started_at: { type: string, format: date-time }
|
|
3753
|
+
entries_imported: { type: integer, minimum: 0 }
|
|
3754
|
+
entries_retired: { type: integer, minimum: 0 }
|
|
3755
|
+
errors:
|
|
3756
|
+
type: array
|
|
3757
|
+
items: { type: string }
|