kaiten-cli 0.3.3__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.
Files changed (99) hide show
  1. kaiten_cli/__init__.py +5 -0
  2. kaiten_cli/__main__.py +9 -0
  3. kaiten_cli/agent_gateway.py +577 -0
  4. kaiten_cli/app.py +1636 -0
  5. kaiten_cli/completion.py +570 -0
  6. kaiten_cli/discovery.py +39 -0
  7. kaiten_cli/errors.py +79 -0
  8. kaiten_cli/i18n.py +92 -0
  9. kaiten_cli/locales/ru.json +2967 -0
  10. kaiten_cli/localized_click.py +160 -0
  11. kaiten_cli/models.py +202 -0
  12. kaiten_cli/profiles.py +316 -0
  13. kaiten_cli/registry/__init__.py +260 -0
  14. kaiten_cli/registry/addons.py +392 -0
  15. kaiten_cli/registry/audit_and_analytics.py +451 -0
  16. kaiten_cli/registry/automation_schema.py +44 -0
  17. kaiten_cli/registry/automation_schemas.json +7417 -0
  18. kaiten_cli/registry/automations.py +342 -0
  19. kaiten_cli/registry/base.py +62 -0
  20. kaiten_cli/registry/blockers.py +320 -0
  21. kaiten_cli/registry/boards.py +520 -0
  22. kaiten_cli/registry/card_relations.py +376 -0
  23. kaiten_cli/registry/card_types.py +363 -0
  24. kaiten_cli/registry/cards.py +1329 -0
  25. kaiten_cli/registry/charts.py +539 -0
  26. kaiten_cli/registry/checklists.py +600 -0
  27. kaiten_cli/registry/columns.py +558 -0
  28. kaiten_cli/registry/comments.py +224 -0
  29. kaiten_cli/registry/custom_directories.py +980 -0
  30. kaiten_cli/registry/custom_properties.py +1460 -0
  31. kaiten_cli/registry/dashboards.py +564 -0
  32. kaiten_cli/registry/documents.py +761 -0
  33. kaiten_cli/registry/external_links.py +128 -0
  34. kaiten_cli/registry/files.py +738 -0
  35. kaiten_cli/registry/github_addon.py +613 -0
  36. kaiten_cli/registry/iterations.py +397 -0
  37. kaiten_cli/registry/lanes.py +205 -0
  38. kaiten_cli/registry/live_contracts.py +361 -0
  39. kaiten_cli/registry/members.py +336 -0
  40. kaiten_cli/registry/module_docs.py +108 -0
  41. kaiten_cli/registry/projects.py +369 -0
  42. kaiten_cli/registry/query.py +129 -0
  43. kaiten_cli/registry/restrictions.py +249 -0
  44. kaiten_cli/registry/roles_and_groups.py +1102 -0
  45. kaiten_cli/registry/scim.py +263 -0
  46. kaiten_cli/registry/service_desk.py +1270 -0
  47. kaiten_cli/registry/snapshot.py +187 -0
  48. kaiten_cli/registry/spaces.py +272 -0
  49. kaiten_cli/registry/subscribers.py +168 -0
  50. kaiten_cli/registry/tags.py +193 -0
  51. kaiten_cli/registry/time_logs.py +353 -0
  52. kaiten_cli/registry/tree.py +352 -0
  53. kaiten_cli/registry/utilities.py +287 -0
  54. kaiten_cli/registry/webhooks.py +265 -0
  55. kaiten_cli/runtime/__init__.py +1 -0
  56. kaiten_cli/runtime/automation_validation.py +115 -0
  57. kaiten_cli/runtime/behaviors.py +787 -0
  58. kaiten_cli/runtime/cache.py +733 -0
  59. kaiten_cli/runtime/client.py +387 -0
  60. kaiten_cli/runtime/endpoints.py +59 -0
  61. kaiten_cli/runtime/executor.py +269 -0
  62. kaiten_cli/runtime/fs_security.py +168 -0
  63. kaiten_cli/runtime/input.py +310 -0
  64. kaiten_cli/runtime/output.py +59 -0
  65. kaiten_cli/runtime/public_validation.py +35 -0
  66. kaiten_cli/runtime/snapshot_build.py +527 -0
  67. kaiten_cli/runtime/snapshot_common.py +363 -0
  68. kaiten_cli/runtime/snapshot_query.py +306 -0
  69. kaiten_cli/runtime/snapshot_store.py +1192 -0
  70. kaiten_cli/runtime/snapshots.py +62 -0
  71. kaiten_cli/runtime/sqlite_errors.py +23 -0
  72. kaiten_cli/runtime/support/__init__.py +1 -0
  73. kaiten_cli/runtime/support/addons.py +953 -0
  74. kaiten_cli/runtime/support/audit.py +70 -0
  75. kaiten_cli/runtime/support/batch.py +234 -0
  76. kaiten_cli/runtime/support/card_move_url.py +213 -0
  77. kaiten_cli/runtime/support/cards.py +174 -0
  78. kaiten_cli/runtime/support/checklists.py +21 -0
  79. kaiten_cli/runtime/support/company_users.py +117 -0
  80. kaiten_cli/runtime/support/dashboards.py +35 -0
  81. kaiten_cli/runtime/support/documents.py +175 -0
  82. kaiten_cli/runtime/support/files.py +561 -0
  83. kaiten_cli/runtime/support/markdown_export.py +525 -0
  84. kaiten_cli/runtime/support/pagination.py +167 -0
  85. kaiten_cli/runtime/support/projects.py +66 -0
  86. kaiten_cli/runtime/support/relations.py +86 -0
  87. kaiten_cli/runtime/support/restrictions.py +62 -0
  88. kaiten_cli/runtime/support/spaces.py +20 -0
  89. kaiten_cli/runtime/support/time_logs.py +61 -0
  90. kaiten_cli/runtime/support/tree.py +140 -0
  91. kaiten_cli/runtime/support/tree_sharing.py +447 -0
  92. kaiten_cli/runtime/trace.py +500 -0
  93. kaiten_cli/runtime/transforms.py +128 -0
  94. kaiten_cli/schema_docs.py +22 -0
  95. kaiten_cli/update_check.py +519 -0
  96. kaiten_cli-0.3.3.dist-info/METADATA +1110 -0
  97. kaiten_cli-0.3.3.dist-info/RECORD +99 -0
  98. kaiten_cli-0.3.3.dist-info/WHEEL +4 -0
  99. kaiten_cli-0.3.3.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,361 @@
1
+ """Live validation contract metadata for the current test tenant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ LIVE_STATUS_PASSED = "live_passed"
9
+ LIVE_STATUS_PASSED_WITH_RUNTIME_FIX = "live_passed_with_runtime_fix"
10
+ LIVE_STATUS_PASSED_AS_EXPECTED_ERROR = "live_passed_as_expected_error"
11
+ LIVE_STATUS_SYNTHETIC_READ = "synthetic_read"
12
+ LIVE_STATUS_POLICY_EXCLUDED = "policy_excluded"
13
+ LIVE_STATUS_NOT_VALIDATED = "live_not_validated"
14
+
15
+ VALID_LIVE_STATUSES = {
16
+ LIVE_STATUS_PASSED,
17
+ LIVE_STATUS_PASSED_WITH_RUNTIME_FIX,
18
+ LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
19
+ LIVE_STATUS_SYNTHETIC_READ,
20
+ LIVE_STATUS_POLICY_EXCLUDED,
21
+ LIVE_STATUS_NOT_VALIDATED,
22
+ }
23
+
24
+
25
+ @dataclass(slots=True, frozen=True)
26
+ class LiveContract:
27
+ status: str
28
+ note: str
29
+ expected_statuses: tuple[int, ...] = ()
30
+
31
+
32
+ _SPECIAL_CONTRACTS: dict[str, LiveContract] = {
33
+ "api-keys.create": LiveContract(
34
+ status=LIVE_STATUS_POLICY_EXCLUDED,
35
+ note="Creating API keys is excluded from live validation because teardown would require testing key deletion.",
36
+ ),
37
+ "api-keys.delete": LiveContract(
38
+ status=LIVE_STATUS_POLICY_EXCLUDED,
39
+ note="Deleting API keys is explicitly excluded from live validation by user instruction.",
40
+ ),
41
+ "boards.delete": LiveContract(
42
+ status=LIVE_STATUS_PASSED_WITH_RUNTIME_FIX,
43
+ note="Sandbox requires the force flag for board deletion; the CLI injects the live-safe request shape.",
44
+ ),
45
+ "projects.cards.list": LiveContract(
46
+ status=LIVE_STATUS_SYNTHETIC_READ,
47
+ note="If GET /projects/{project_id}/cards returns 405, the CLI falls back to GET /projects/{project_id}?with_cards_data=true and extracts the embedded cards list.",
48
+ expected_statuses=(405,),
49
+ ),
50
+ "removed-boards.list": LiveContract(
51
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
52
+ note="Sandbox returns 405 for recycle-bin board listing; the live suite validates that contract explicitly.",
53
+ expected_statuses=(405,),
54
+ ),
55
+ "removed-cards.list": LiveContract(
56
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
57
+ note="Sandbox returns 405 for recycle-bin card listing; the live suite validates that contract explicitly.",
58
+ expected_statuses=(405,),
59
+ ),
60
+ "checklists.list": LiveContract(
61
+ status=LIVE_STATUS_SYNTHETIC_READ,
62
+ note="The collection GET returned 405 on the tested tenant and was also reported on production in kaiten-mcp PR #4; the CLI extracts checklists from GET /cards/{card_id}.",
63
+ expected_statuses=(405,),
64
+ ),
65
+ "checklist-items.list": LiveContract(
66
+ status=LIVE_STATUS_SYNTHETIC_READ,
67
+ note="The collection GET returned 405 on tested instances; the CLI extracts items from GET /cards/{card_id}/checklists/{checklist_id} and preserves API errors.",
68
+ expected_statuses=(405,),
69
+ ),
70
+ "card-subscribers.list": LiveContract(
71
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
72
+ note="Sandbox returns 405 for card subscriber listing; the live suite validates the expected error path.",
73
+ expected_statuses=(405,),
74
+ ),
75
+ "column-subscribers.list": LiveContract(
76
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
77
+ note="Sandbox returns 405 for column subscriber listing; the live suite validates the expected error path.",
78
+ expected_statuses=(405,),
79
+ ),
80
+ "sprints.create": LiveContract(
81
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
82
+ note="Sprint creation is permission-dependent on sandbox; the live suite accepts either success or a documented 403/405 error.",
83
+ expected_statuses=(403, 405),
84
+ ),
85
+ "sprints.list": LiveContract(
86
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
87
+ note="Sprint listing is permission-dependent on sandbox; the live suite accepts either success or a documented 403/405 error.",
88
+ expected_statuses=(403, 405),
89
+ ),
90
+ "sprints.get": LiveContract(
91
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
92
+ note="When sprint creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel sprint id.",
93
+ expected_statuses=(403, 404, 405),
94
+ ),
95
+ "sprints.update": LiveContract(
96
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
97
+ note="When sprint creation is unavailable or the created sprint id cannot be resolved, sandbox may return 403/404/405 or 500 on a sentinel sprint id; the live suite validates that documented defect contract explicitly.",
98
+ expected_statuses=(403, 404, 405, 500),
99
+ ),
100
+ "sprints.delete": LiveContract(
101
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
102
+ note="Sprint deletion is often unavailable on sandbox; the live suite accepts the documented 403/404/405 contract.",
103
+ expected_statuses=(403, 404, 405),
104
+ ),
105
+ "webhooks.get": LiveContract(
106
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
107
+ note="Webhook GET may return 404/405 even after successful creation; the live suite validates that contract explicitly.",
108
+ expected_statuses=(404, 405),
109
+ ),
110
+ "webhooks.delete": LiveContract(
111
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
112
+ note="Webhook DELETE may return 404/405 even after successful creation; the live suite validates that contract explicitly.",
113
+ expected_statuses=(404, 405),
114
+ ),
115
+ "automations.get": LiveContract(
116
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
117
+ note="Automation GET-single may return 405 even after successful creation; the live suite validates that contract explicitly.",
118
+ expected_statuses=(405,),
119
+ ),
120
+ "automations.create": LiveContract(
121
+ status=LIVE_STATUS_PASSED,
122
+ note="Automation creation passes on sandbox when the payload matches the known live-valid add_assignee shape derived from kaiten-mcp e2e.",
123
+ ),
124
+ "automations.update": LiveContract(
125
+ status=LIVE_STATUS_PASSED,
126
+ note="Automation update passes on sandbox for automations created with the known live-valid add_assignee payload shape.",
127
+ ),
128
+ "automations.delete": LiveContract(
129
+ status=LIVE_STATUS_PASSED,
130
+ note="Automation delete passes on sandbox for automations created during live validation; cleanup is verified.",
131
+ ),
132
+ "automations.copy": LiveContract(
133
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
134
+ note="Automation copy remains sandbox-dependent even with a live-valid source automation; the live suite accepts success or a documented 400/403/404/405 contract.",
135
+ expected_statuses=(400, 403, 404, 405),
136
+ ),
137
+ "workflows.create": LiveContract(
138
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
139
+ note="Workflow creation is permission-dependent on sandbox; the live suite accepts either success or a documented 403/405 error.",
140
+ expected_statuses=(403, 405),
141
+ ),
142
+ "workflows.get": LiveContract(
143
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
144
+ note="When workflow creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel workflow id.",
145
+ expected_statuses=(403, 404, 405),
146
+ ),
147
+ "workflows.update": LiveContract(
148
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
149
+ note="When workflow creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel workflow id.",
150
+ expected_statuses=(403, 404, 405),
151
+ ),
152
+ "workflows.delete": LiveContract(
153
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
154
+ note="When workflow creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel workflow id.",
155
+ expected_statuses=(403, 404, 405),
156
+ ),
157
+ "service-desk.users.set-temp-password": LiveContract(
158
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
159
+ note="Temporary password generation may succeed or return a documented 403/404/405 sandbox error; the live suite accepts both outcomes.",
160
+ expected_statuses=(403, 404, 405),
161
+ ),
162
+ "service-desk.users.update": LiveContract(
163
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
164
+ note="The current live account is not a Service Desk user, so update may return 400 'Should be service desk user'; the live suite validates that documented contract.",
165
+ expected_statuses=(400, 403, 404, 405),
166
+ ),
167
+ "service-desk.organization-users.update": LiveContract(
168
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
169
+ note="Updating Service Desk organization-user permissions remains sandbox-dependent; the live suite accepts success or a documented 400/403/404/405 contract.",
170
+ expected_statuses=(400, 403, 404, 405),
171
+ ),
172
+ "service-desk.requests.create": LiveContract(
173
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
174
+ note="Service Desk request creation is permission-dependent; the live suite accepts either success or a documented 400/403/404/405 contract.",
175
+ expected_statuses=(400, 403, 404, 405),
176
+ ),
177
+ "service-desk.requests.get": LiveContract(
178
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
179
+ note="When request creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel request id.",
180
+ expected_statuses=(403, 404, 405),
181
+ ),
182
+ "service-desk.requests.update": LiveContract(
183
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
184
+ note="When request creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel request id.",
185
+ expected_statuses=(403, 404, 405),
186
+ ),
187
+ "service-desk.requests.delete": LiveContract(
188
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
189
+ note="When request creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel request id.",
190
+ expected_statuses=(403, 404, 405),
191
+ ),
192
+ "service-desk.sla-rules.create": LiveContract(
193
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
194
+ note="SLA rule creation is permission- and schema-dependent; the live suite accepts either success or a documented 400/403/404/405 contract.",
195
+ expected_statuses=(400, 403, 404, 405),
196
+ ),
197
+ "service-desk.sla-rules.update": LiveContract(
198
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
199
+ note="When SLA-rule creation is unavailable, the live suite validates the documented 400/403/404/405 error contract on a sentinel rule id.",
200
+ expected_statuses=(400, 403, 404, 405),
201
+ ),
202
+ "service-desk.sla-rules.delete": LiveContract(
203
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
204
+ note="When SLA-rule creation is unavailable, the live suite validates the documented 400/403/404/405 error contract on a sentinel rule id.",
205
+ expected_statuses=(400, 403, 404, 405),
206
+ ),
207
+ "compute-jobs.cancel": LiveContract(
208
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
209
+ note="Canceling a compute job can legitimately return 400/404/409 depending on backend state; the live suite accepts that contract.",
210
+ expected_statuses=(400, 404, 409),
211
+ ),
212
+ "user-timers.list": LiveContract(
213
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
214
+ note="User-timer listing remains sandbox-dependent; the live suite accepts either success or a documented 403/405 error path.",
215
+ expected_statuses=(403, 405),
216
+ ),
217
+ "user-timers.create": LiveContract(
218
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
219
+ note="User-timer creation remains sandbox-dependent; the live suite accepts either success or a documented 400/403/405/409 contract.",
220
+ expected_statuses=(400, 403, 405, 409),
221
+ ),
222
+ "user-timers.get": LiveContract(
223
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
224
+ note="When timer creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel timer id.",
225
+ expected_statuses=(403, 404, 405),
226
+ ),
227
+ "user-timers.update": LiveContract(
228
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
229
+ note="When timer creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel timer id.",
230
+ expected_statuses=(403, 404, 405),
231
+ ),
232
+ "user-timers.delete": LiveContract(
233
+ status=LIVE_STATUS_PASSED_AS_EXPECTED_ERROR,
234
+ note="When timer creation is unavailable, the live suite validates the documented 403/404/405 error contract on a sentinel timer id.",
235
+ expected_statuses=(403, 404, 405),
236
+ ),
237
+ "addons.list": LiveContract(
238
+ status=LIVE_STATUS_NOT_VALIDATED,
239
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
240
+ ),
241
+ "addons.uid": LiveContract(
242
+ status=LIVE_STATUS_NOT_VALIDATED,
243
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
244
+ ),
245
+ "company-addons.list": LiveContract(
246
+ status=LIVE_STATUS_NOT_VALIDATED,
247
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
248
+ ),
249
+ "space-addons.list": LiveContract(
250
+ status=LIVE_STATUS_NOT_VALIDATED,
251
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
252
+ ),
253
+ "space-addons.install": LiveContract(
254
+ status=LIVE_STATUS_NOT_VALIDATED,
255
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
256
+ ),
257
+ "space-addons.uninstall": LiveContract(
258
+ status=LIVE_STATUS_NOT_VALIDATED,
259
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
260
+ ),
261
+ "card-addon-data.get": LiveContract(
262
+ status=LIVE_STATUS_NOT_VALIDATED,
263
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
264
+ ),
265
+ "card-addon-data.set": LiveContract(
266
+ status=LIVE_STATUS_NOT_VALIDATED,
267
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
268
+ ),
269
+ "user-addon-data.get": LiveContract(
270
+ status=LIVE_STATUS_NOT_VALIDATED,
271
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
272
+ ),
273
+ "user-addon-data.set": LiveContract(
274
+ status=LIVE_STATUS_NOT_VALIDATED,
275
+ note="Addon commands were added after the last live campaign; the live suite exercises them on read and documented-error paths, but no full live run has confirmed them yet.",
276
+ ),
277
+ "github-addon.pulls.list": LiveContract(
278
+ status=LIVE_STATUS_NOT_VALIDATED,
279
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
280
+ ),
281
+ "github-addon.pulls.attach": LiveContract(
282
+ status=LIVE_STATUS_NOT_VALIDATED,
283
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
284
+ ),
285
+ "github-addon.pulls.detach": LiveContract(
286
+ status=LIVE_STATUS_NOT_VALIDATED,
287
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
288
+ ),
289
+ "github-addon.branches.list": LiveContract(
290
+ status=LIVE_STATUS_NOT_VALIDATED,
291
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
292
+ ),
293
+ "github-addon.branches.attach": LiveContract(
294
+ status=LIVE_STATUS_NOT_VALIDATED,
295
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
296
+ ),
297
+ "github-addon.branches.detach": LiveContract(
298
+ status=LIVE_STATUS_NOT_VALIDATED,
299
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
300
+ ),
301
+ "github-addon.commits.list": LiveContract(
302
+ status=LIVE_STATUS_NOT_VALIDATED,
303
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
304
+ ),
305
+ "github-addon.commits.attach": LiveContract(
306
+ status=LIVE_STATUS_NOT_VALIDATED,
307
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
308
+ ),
309
+ "github-addon.commits.detach": LiveContract(
310
+ status=LIVE_STATUS_NOT_VALIDATED,
311
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
312
+ ),
313
+ "github-addon.issues.list": LiveContract(
314
+ status=LIVE_STATUS_NOT_VALIDATED,
315
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
316
+ ),
317
+ "github-addon.issues.attach": LiveContract(
318
+ status=LIVE_STATUS_NOT_VALIDATED,
319
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
320
+ ),
321
+ "github-addon.issues.detach": LiveContract(
322
+ status=LIVE_STATUS_NOT_VALIDATED,
323
+ note="GitHub addon commands were added after the last live campaign. The live suite covers reads and dry runs; a real attach/detach needs a tenant with the addon installed and has not been live-validated yet.",
324
+ ),
325
+ }
326
+
327
+
328
+ _SPECIAL_CONTRACTS.update(
329
+ {
330
+ f"restrictions.{action}": LiveContract(
331
+ status=LIVE_STATUS_NOT_VALIDATED,
332
+ note="Restrictions are an undocumented server-code extension. The opt-in live scenario has not been run; compatibility and cleanup remain unverified on a live tenant.",
333
+ )
334
+ for action in ("list", "get", "create", "update", "delete", "copy")
335
+ }
336
+ )
337
+
338
+
339
+ def get_live_contract(canonical_name: str) -> LiveContract:
340
+ return _SPECIAL_CONTRACTS.get(
341
+ canonical_name,
342
+ LiveContract(
343
+ status=LIVE_STATUS_PASSED,
344
+ note="Validated on the normal success path in the full live suite.",
345
+ ),
346
+ )
347
+
348
+
349
+ def iter_special_live_contracts() -> tuple[tuple[str, LiveContract], ...]:
350
+ return tuple(sorted(_SPECIAL_CONTRACTS.items()))
351
+
352
+
353
+ def has_special_live_contract(canonical_name: str) -> bool:
354
+ return canonical_name in _SPECIAL_CONTRACTS
355
+
356
+
357
+ LIVE_POLICY_EXCLUSIONS = {
358
+ name: contract.note
359
+ for name, contract in _SPECIAL_CONTRACTS.items()
360
+ if contract.status == LIVE_STATUS_POLICY_EXCLUDED
361
+ }