mainsequence 8.1.20.dev14__py3-none-any.whl → 8.1.20.dev16__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.
@@ -6,7 +6,6 @@ import json
6
6
  import pathlib
7
7
  import time
8
8
  import uuid
9
- from enum import Enum
10
9
  from typing import Any, ClassVar, Literal
11
10
 
12
11
  import requests
@@ -31,6 +30,7 @@ from .observability import (
31
30
  PublicLogLevel,
32
31
  )
33
32
  from .utils import make_request, serialize_to_json
33
+ from .value_sets import OpenStrEnum, OpenValueSet
34
34
 
35
35
  DEFAULT_AGENT_SESSION_LONG_REQUEST_TIMEOUT = (5.0, 900.0)
36
36
  DEFAULT_AGENT_RUNTIME_ACCESS_CACHE_TTL_SECONDS = 60.0
@@ -50,7 +50,7 @@ MIN_RUNTIME_INTERACTION_RETRY_SECONDS = 0.5
50
50
  MAX_RUNTIME_INTERACTION_RETRY_SECONDS = 30.0
51
51
 
52
52
 
53
- class AgentSessionStatus(str, Enum):
53
+ class AgentSessionStatus(OpenStrEnum):
54
54
  PENDING = "pending"
55
55
  RUNNING = "running"
56
56
  COMPLETED = "completed"
@@ -58,7 +58,7 @@ class AgentSessionStatus(str, Enum):
58
58
  CANCELED = "canceled"
59
59
 
60
60
 
61
- class A2AResponseKind(str, Enum):
61
+ class A2AResponseKind(OpenStrEnum):
62
62
  MESSAGE = "message"
63
63
  TASK = "task"
64
64
 
@@ -121,12 +121,12 @@ class A2AMessageSendResult(BasePydanticModel):
121
121
  return A2AResponseKind.TASK if self.task is not None else A2AResponseKind.MESSAGE
122
122
 
123
123
 
124
- class AgentHarnessKind(str, Enum):
124
+ class AgentHarnessKind(OpenStrEnum):
125
125
  PI = "pi"
126
126
  TAU = "tau"
127
127
 
128
128
 
129
- class AgentHarnessProtocol(str, Enum):
129
+ class AgentHarnessProtocol(OpenStrEnum):
130
130
  PI_CHECKPOINT_V1 = "pi-checkpoint-v1"
131
131
  TAU_SESSION_V1 = "tau-session-v1"
132
132
 
@@ -136,13 +136,15 @@ class AgentRuntimeUpdateRemediation(BasePydanticModel):
136
136
 
137
137
 
138
138
  class AgentRuntimeUpdate(BasePydanticModel):
139
- state: Literal[
140
- "current",
141
- "update_required",
142
- "updating",
143
- "update_failed",
144
- "not_deployed",
145
- "unknown",
139
+ state: OpenValueSet[
140
+ Literal[
141
+ "current",
142
+ "update_required",
143
+ "updating",
144
+ "update_failed",
145
+ "not_deployed",
146
+ "unknown",
147
+ ]
146
148
  ]
147
149
  needs_redeploy: bool | None
148
150
  remediation: AgentRuntimeUpdateRemediation | None
@@ -279,7 +281,7 @@ class AgentRuntimeInteractionNotice(BasePydanticModel):
279
281
  class AgentRuntimeInteractionAction(BasePydanticModel):
280
282
  model_config = ConfigDict(extra="allow")
281
283
 
282
- type: Literal["update_agent_runtime", "retry_runtime_wake"]
284
+ type: OpenValueSet[Literal["update_agent_runtime", "retry_runtime_wake"]]
283
285
  label: str
284
286
  confirmation_message: str
285
287
  interaction_revision: str
@@ -299,16 +301,18 @@ class AgentRuntimeInteractionOperation(BasePydanticModel):
299
301
  class AgentRuntimeInteraction(BasePydanticModel):
300
302
  model_config = ConfigDict(extra="allow")
301
303
 
302
- state: Literal[
303
- "ready",
304
- "warning",
305
- "checking",
306
- "starting",
307
- "waking",
308
- "update_required",
309
- "updating",
310
- "update_failed",
311
- "unavailable",
304
+ state: OpenValueSet[
305
+ Literal[
306
+ "ready",
307
+ "warning",
308
+ "checking",
309
+ "starting",
310
+ "waking",
311
+ "update_required",
312
+ "updating",
313
+ "update_failed",
314
+ "unavailable",
315
+ ]
312
316
  ]
313
317
  can_submit: bool
314
318
  notice: AgentRuntimeInteractionNotice | None
@@ -324,29 +328,33 @@ class AgentRuntimePresenceReplicas(BasePydanticModel):
324
328
 
325
329
  class AgentRuntimePresenceWake(BasePydanticModel):
326
330
  operation_uid: str
327
- state: Literal[
328
- "requested",
329
- "in_progress",
330
- "serving",
331
- "failed",
332
- "expired",
333
- "superseded",
331
+ state: OpenValueSet[
332
+ Literal[
333
+ "requested",
334
+ "in_progress",
335
+ "serving",
336
+ "failed",
337
+ "expired",
338
+ "superseded",
339
+ ]
334
340
  ]
335
341
  requested_at: str
336
342
  deadline_at: str
337
343
 
338
344
 
339
345
  class AgentRuntimePresence(BasePydanticModel):
340
- phase: Literal[
341
- "not_deployed",
342
- "observing",
343
- "idle",
344
- "provisioning",
345
- "pulling_image",
346
- "starting",
347
- "serving",
348
- "redeploying",
349
- "failed",
346
+ phase: OpenValueSet[
347
+ Literal[
348
+ "not_deployed",
349
+ "observing",
350
+ "idle",
351
+ "provisioning",
352
+ "pulling_image",
353
+ "starting",
354
+ "serving",
355
+ "redeploying",
356
+ "failed",
357
+ ]
350
358
  ]
351
359
  replicas: AgentRuntimePresenceReplicas
352
360
  detail: str
@@ -361,7 +369,7 @@ class AgentSessionRuntimeAccess(BasePydanticModel):
361
369
  None,
362
370
  description="Canonical public UID of the coding-agent service that owns the runtime.",
363
371
  )
364
- mode: Literal["token", "unavailable"] = Field(
372
+ mode: OpenValueSet[Literal["token", "unavailable"]] = Field(
365
373
  "token",
366
374
  description="Runtime access mode returned by the backend.",
367
375
  )
@@ -862,7 +870,7 @@ class TauAgentSessionInsightsSession(BasePydanticModel):
862
870
  agent_session_id: str = Field(..., alias="agentSessionId")
863
871
  session_id: str | None = Field(None, alias="sessionId")
864
872
  thread_id: str | None = Field(None, alias="threadId")
865
- status: Literal["running", "completed", "error"]
873
+ status: OpenValueSet[Literal["running", "completed", "error"]]
866
874
  started_at: datetime.datetime | None = Field(None, alias="startedAt")
867
875
  updated_at: datetime.datetime | None = Field(None, alias="updatedAt")
868
876
  last_error: str | None = Field(None, alias="lastError")
@@ -11,11 +11,16 @@ from . import utils as client_utils
11
11
  from .base import BasePydanticModel
12
12
  from .exceptions import raise_for_response
13
13
  from .utils import make_request
14
+ from .value_sets import OpenValueSet
14
15
 
15
16
  GitHubIssueState = Literal["open", "closed"]
16
17
  GitHubIssueStateReason = Literal["completed", "not_planned", "reopened"]
17
- GitHubIssueOperationType = Literal["create_issue", "update_issue", "create_comment"]
18
- GitHubIssueOperationStatus = Literal["pending", "succeeded", "failed", "unknown"]
18
+ GitHubIssueOperationType = OpenValueSet[
19
+ Literal["create_issue", "update_issue", "create_comment"]
20
+ ]
21
+ GitHubIssueOperationStatus = OpenValueSet[
22
+ Literal["pending", "succeeded", "failed", "unknown"]
23
+ ]
19
24
 
20
25
  _IDEMPOTENCY_KEY_PATTERN = re.compile(r"^[A-Za-z0-9._:-]+$")
21
26
 
@@ -115,7 +120,7 @@ class _GitHubIssueProjection(BasePydanticModel):
115
120
  issue_number: int
116
121
  html_url: str
117
122
  title: str
118
- state: GitHubIssueState
123
+ state: OpenValueSet[GitHubIssueState]
119
124
  state_reason: str | None
120
125
  created_at: datetime.datetime
121
126
  updated_at: datetime.datetime
@@ -61,6 +61,7 @@ from ..utils import (
61
61
  make_request,
62
62
  serialize_to_json,
63
63
  )
64
+ from ..value_sets import OpenValueSet
64
65
 
65
66
  DUCK_DB = "duck_db"
66
67
  SQLITE = "sqlite"
@@ -1059,7 +1060,7 @@ class MetaTableCascadeDeletedMetaTable(BasePydanticModel):
1059
1060
  meta_table_uid: str
1060
1061
  physical_schema: str | None = None
1061
1062
  physical_table_name: str | None = None
1062
- management_mode: MetaTableManagementMode
1063
+ management_mode: OpenValueSet[MetaTableManagementMode]
1063
1064
  physical_table_drop_scheduled: bool
1064
1065
  physical_table_drop_cascade: bool
1065
1066
  schema_management_protection_overridden: bool
@@ -1109,7 +1110,7 @@ class DataSource(BasePydanticModel, BaseObjectOrm):
1109
1110
  )
1110
1111
  class_type: str | None = None
1111
1112
  status: str | None = None
1112
- storage_access_mode: StorageAccessMode | None = Field(
1113
+ storage_access_mode: OpenValueSet[StorageAccessMode] | None = Field(
1113
1114
  default="read_write",
1114
1115
  description="Runtime storage access gate: read_write, read_only, or disabled.",
1115
1116
  )
@@ -1369,9 +1370,9 @@ class MetaTable(
1369
1370
  namespace: str | None = None
1370
1371
  description: str | None = None
1371
1372
  labels: list[str] = Field(default_factory=list)
1372
- management_mode: MetaTableManagementMode
1373
- provisioning_status: Literal["reserved", "active"] = "active"
1374
- schema_management_mode: MetaTableSchemaManagementMode = "backend_managed"
1373
+ management_mode: OpenValueSet[MetaTableManagementMode]
1374
+ provisioning_status: OpenValueSet[Literal["reserved", "active"]] = "active"
1375
+ schema_management_mode: OpenValueSet[MetaTableSchemaManagementMode] = "backend_managed"
1375
1376
  schema_management: dict[str, Any] = Field(default_factory=dict)
1376
1377
  migration_package: str | None = None
1377
1378
  migration_namespace: str | None = None
@@ -2544,7 +2545,7 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
2544
2545
  index_min=index_min,
2545
2546
  multi_index_column_stats=result.get("multi_index_column_stats"),
2546
2547
  ),
2547
- direct_dependency_uids=result.get("direct_dependency_uids"),
2548
+ upstream_update_uids=result.get("upstream_update_uids"),
2548
2549
  )
2549
2550
  return table_update_run
2550
2551
 
@@ -4576,7 +4577,16 @@ class _TableUpdateRunFields:
4576
4577
  )
4577
4578
  # extra fields for local control
4578
4579
  update_statistics: BaseUpdateStatistics | None = None
4579
- direct_dependency_uids: list[str] | None = None
4580
+ upstream_update_uids: list[str] | None = Field(
4581
+ None,
4582
+ description=(
4583
+ "Uids of the update's direct upstream dependencies, as set-start-of-execution "
4584
+ "reports them. The backend reads them off the update node's "
4585
+ "`upstream_update_dependencies` edges, which the time-index table updater hard "
4586
+ "cut renamed from `downstream_direct_dependencies`; this field carries that "
4587
+ "canonical name rather than the pre-cut one."
4588
+ ),
4589
+ )
4580
4590
 
4581
4591
 
4582
4592
  class TableUpdateRun(_TableUpdateRunFields, BasePydanticModel, BaseObjectOrm):
@@ -30,6 +30,7 @@ from .metatables import DataSource as _DataSource
30
30
  from .utils import (
31
31
  make_request,
32
32
  )
33
+ from .value_sets import OpenValueSet
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from .github_issues import GitHubIssueMutationResult, GitHubIssuePage, GitHubIssueState
@@ -508,7 +509,7 @@ class CodeRepositoryBranch(BasePydanticModel, BaseObjectOrm):
508
509
  github_repository_binding_uid: str | None = None
509
510
  latest_git_version: str = ""
510
511
  is_initialized: bool
511
- provisioning_status: Literal["CREATING", "READY", "FAILED"]
512
+ provisioning_status: OpenValueSet[Literal["CREATING", "READY", "FAILED"]]
512
513
  provisioning_job_run_uid: str | None
513
514
  provisioning_error_code: str
514
515
  provisioning_error_detail: str
@@ -726,7 +727,7 @@ class GitHubRepositoryBinding(BasePydanticModel, BaseObjectOrm):
726
727
 
727
728
 
728
729
  class CodeRepositoryImageSourceProvenance(BasePydanticModel):
729
- verification_state: Literal["verified", "unverified", "building", "failed"]
730
+ verification_state: OpenValueSet[Literal["verified", "unverified", "building", "failed"]]
730
731
  code_repository_uid: str | None = None
731
732
  code_repository_branch_uid: str | None = None
732
733
  github_repository_binding_uid: str | None = None
@@ -5,7 +5,6 @@ import json
5
5
  import time
6
6
  from collections.abc import Collection
7
7
  from decimal import Decimal
8
- from enum import Enum
9
8
  from pathlib import PurePosixPath
10
9
  from typing import Any, ClassVar, Literal
11
10
  from uuid import UUID
@@ -41,6 +40,7 @@ from .observability import (
41
40
  PublicLogLevel,
42
41
  )
43
42
  from .utils import make_request
43
+ from .value_sets import OpenStrEnum, OpenValueSet
44
44
 
45
45
 
46
46
  def get_model_class(model_class: str):
@@ -1156,27 +1156,34 @@ class CodeRepositoryResource(CurrentCodeRepositoryBranchCollectionMixin, BaseObj
1156
1156
  )
1157
1157
 
1158
1158
 
1159
- class ResourceReleaseKind(str, Enum):
1159
+ class ResourceReleaseKind(OpenStrEnum):
1160
+ """The backend's canonical release-kind vocabulary.
1161
+
1162
+ Open (ADR 0033): a kind a later backend adds is read as sent, rather than
1163
+ failing the release that carries it and the listing that release is in.
1164
+ """
1165
+
1160
1166
  AGENT = "agent"
1161
1167
  FAST_API = "fastapi"
1168
+ HARNESS_AGENT = "harness_agent"
1162
1169
  STATIC_SITE = "static_site"
1163
1170
 
1164
1171
 
1165
1172
  class ResourceReleaseRuntimeRouting(BasePydanticModel):
1166
- state: Literal["routable", "unavailable"]
1173
+ state: OpenValueSet[Literal["routable", "unavailable"]]
1167
1174
  active_revision_uid: str | None = None
1168
1175
 
1169
1176
 
1170
1177
  class ResourceReleaseRuntimeNotice(BasePydanticModel):
1171
1178
  code: str
1172
- severity: Literal["info", "warning", "error"]
1179
+ severity: OpenValueSet[Literal["info", "warning", "error"]]
1173
1180
  title: str
1174
1181
  message: str
1175
1182
 
1176
1183
 
1177
1184
  class ResourceReleaseRuntimeOperation(BasePydanticModel):
1178
1185
  uid: str
1179
- status: Literal["queued", "running", "succeeded", "failed", "superseded"]
1186
+ status: OpenValueSet[Literal["queued", "running", "succeeded", "failed", "superseded"]]
1180
1187
  created_at: datetime.datetime
1181
1188
  started_at: datetime.datetime | None = None
1182
1189
  finished_at: datetime.datetime | None = None
@@ -1184,7 +1191,7 @@ class ResourceReleaseRuntimeOperation(BasePydanticModel):
1184
1191
 
1185
1192
 
1186
1193
  class ResourceReleaseRuntimeAdmission(BasePydanticModel):
1187
- state: Literal["ready", "waking", "unavailable"]
1194
+ state: OpenValueSet[Literal["ready", "waking", "unavailable"]]
1188
1195
  can_request: bool
1189
1196
  notice: ResourceReleaseRuntimeNotice | None = None
1190
1197
  operation: ResourceReleaseRuntimeOperation | None = None
@@ -1198,24 +1205,28 @@ class ResourceReleaseRuntimeReplicas(BasePydanticModel):
1198
1205
 
1199
1206
  class ResourceReleaseRuntimeWake(BasePydanticModel):
1200
1207
  operation_uid: str
1201
- state: Literal[
1202
- "requested", "in_progress", "serving", "failed", "expired", "superseded"
1208
+ state: OpenValueSet[
1209
+ Literal[
1210
+ "requested", "in_progress", "serving", "failed", "expired", "superseded"
1211
+ ]
1203
1212
  ]
1204
1213
  requested_at: datetime.datetime
1205
1214
  deadline_at: datetime.datetime
1206
1215
 
1207
1216
 
1208
1217
  class ResourceReleaseRuntimePresence(BasePydanticModel):
1209
- phase: Literal[
1210
- "not_deployed",
1211
- "idle",
1212
- "observing",
1213
- "provisioning",
1214
- "pulling_image",
1215
- "starting",
1216
- "serving",
1217
- "redeploying",
1218
- "failed",
1218
+ phase: OpenValueSet[
1219
+ Literal[
1220
+ "not_deployed",
1221
+ "idle",
1222
+ "observing",
1223
+ "provisioning",
1224
+ "pulling_image",
1225
+ "starting",
1226
+ "serving",
1227
+ "redeploying",
1228
+ "failed",
1229
+ ]
1219
1230
  ]
1220
1231
  replicas: ResourceReleaseRuntimeReplicas
1221
1232
  detail: str
@@ -1225,7 +1236,9 @@ class ResourceReleaseRuntimePresence(BasePydanticModel):
1225
1236
 
1226
1237
  class ResourceReleaseRuntimeAccess(BasePydanticModel):
1227
1238
  resource_release_uid: str
1228
- release_kind: Literal["fastapi", "harness_agent"]
1239
+ # The canonical vocabulary ResourceRelease.release_kind reads (#119): this
1240
+ # payload names a release kind, so it names it the same way.
1241
+ release_kind: ResourceReleaseKind
1229
1242
  routing: ResourceReleaseRuntimeRouting
1230
1243
  runtime_access: ResourceReleaseRuntimeAdmission
1231
1244
  runtime_presence: ResourceReleaseRuntimePresence
@@ -1512,11 +1525,14 @@ class ResourceRelease(
1512
1525
  normalized_release_kind = release_kind.value
1513
1526
  else:
1514
1527
  normalized_release_kind = Job._normalize_str(release_kind)
1515
- allowed_release_kinds = {kind.value for kind in ResourceReleaseKind}
1516
- if normalized_release_kind not in allowed_release_kinds:
1517
- raise ValueError(
1518
- "release_kind must be one of: " + ", ".join(sorted(allowed_release_kinds)) + "."
1519
- )
1528
+ # What the SDK sends stays closed (ADR 0033). ResourceReleaseKind reads an
1529
+ # undeclared kind out of a response, but iterating it yields only the kinds
1530
+ # this release declares, so a kind built here is still checked against them.
1531
+ allowed_release_kinds = {kind.value for kind in ResourceReleaseKind}
1532
+ if normalized_release_kind not in allowed_release_kinds:
1533
+ raise ValueError(
1534
+ "release_kind must be one of: " + ", ".join(sorted(allowed_release_kinds)) + "."
1535
+ )
1520
1536
 
1521
1537
  normalized_compute = Job._validate_and_normalize_compute_fields(
1522
1538
  cpu_request=cpu_request,
@@ -1617,26 +1633,30 @@ class DeploymentRunStep(BaseModel):
1617
1633
  sequence: PositiveInt
1618
1634
  key: str
1619
1635
  name: str = ""
1620
- kind: Literal[
1621
- "source",
1622
- "image_build",
1623
- "validation",
1624
- "runtime_deploy",
1625
- "runtime_readiness",
1626
- "publish",
1627
- "cleanup",
1628
- "orchestration",
1636
+ kind: OpenValueSet[
1637
+ Literal[
1638
+ "source",
1639
+ "image_build",
1640
+ "validation",
1641
+ "runtime_deploy",
1642
+ "runtime_readiness",
1643
+ "publish",
1644
+ "cleanup",
1645
+ "orchestration",
1646
+ ]
1629
1647
  ]
1630
1648
  required: bool = True
1631
- state: Literal[
1632
- "pending",
1633
- "running",
1634
- "succeeded",
1635
- "failed",
1636
- "cancelled",
1637
- "skipped",
1638
- "blocked",
1639
- "superseded",
1649
+ state: OpenValueSet[
1650
+ Literal[
1651
+ "pending",
1652
+ "running",
1653
+ "succeeded",
1654
+ "failed",
1655
+ "cancelled",
1656
+ "skipped",
1657
+ "blocked",
1658
+ "superseded",
1659
+ ]
1640
1660
  ]
1641
1661
  outcome: str = ""
1642
1662
  artifact_context: dict[str, Any] = Field(default_factory=dict)
@@ -1658,12 +1678,14 @@ class DeploymentRunBillingComponents(BasePydanticModel):
1658
1678
  image_registry_service: Decimal | None = Field(max_digits=18, decimal_places=6)
1659
1679
 
1660
1680
 
1661
- DeploymentRunPricingState = Literal[
1662
- "priced",
1663
- "partial",
1664
- "pending",
1665
- "unavailable",
1666
- "failed",
1681
+ DeploymentRunPricingState = OpenValueSet[
1682
+ Literal[
1683
+ "priced",
1684
+ "partial",
1685
+ "pending",
1686
+ "unavailable",
1687
+ "failed",
1688
+ ]
1667
1689
  ]
1668
1690
 
1669
1691
 
@@ -1700,14 +1722,14 @@ class DeploymentRunLogEntry(BaseModel):
1700
1722
  timestamp: datetime.datetime | None = None
1701
1723
  step_uid: str | None = None
1702
1724
  source: str
1703
- stream: Literal["stdout", "stderr"]
1704
- level: Literal["debug", "info", "warning", "error"]
1725
+ stream: OpenValueSet[Literal["stdout", "stderr"]]
1726
+ level: OpenValueSet[Literal["debug", "info", "warning", "error"]]
1705
1727
  text: str = ""
1706
1728
 
1707
1729
 
1708
1730
  class DeploymentRunLogSource(BaseModel):
1709
1731
  source: str
1710
- state: Literal["pending", "available", "partial", "expired", "unavailable"]
1732
+ state: OpenValueSet[Literal["pending", "available", "partial", "expired", "unavailable"]]
1711
1733
 
1712
1734
 
1713
1735
  class DeploymentRunLogPage(BaseModel):
@@ -22,6 +22,7 @@ from .utils import (
22
22
  DEFAULT_TIMEOUT,
23
23
  make_request,
24
24
  )
25
+ from .value_sets import OpenValueSet
25
26
 
26
27
  _CURRENT_AUTH_HEADERS: ContextVar[Mapping[str, Any] | None] = ContextVar(
27
28
  "_CURRENT_AUTH_HEADERS",
@@ -528,7 +529,7 @@ class Notification(DetailActionObjectMixin, BasePydanticModel, UserApiBaseObject
528
529
  title="Notification UID",
529
530
  description="Stable public unique identifier of the notification.",
530
531
  )
531
- source: Literal["system", "organization"] | None = Field(
532
+ source: OpenValueSet[Literal["system", "organization"]] | None = Field(
532
533
  None,
533
534
  title="Notification Source",
534
535
  description="Source scope of the notification.",
@@ -544,7 +545,7 @@ class Notification(DetailActionObjectMixin, BasePydanticModel, UserApiBaseObject
544
545
  title="Source Organization UID",
545
546
  description="Public UID of the organization that owns the notification.",
546
547
  )
547
- type: Literal["UR", "IM", "IN"] = Field(
548
+ type: OpenValueSet[Literal["UR", "IM", "IN"]] = Field(
548
549
  "IN",
549
550
  title="Notification Type",
550
551
  description="Stored notification priority code.",
@@ -914,7 +915,7 @@ class ShareableAccessState(BasePydanticModel):
914
915
  description="Backend object type identifier for the shareable object.",
915
916
  examples=["tdag.constant"],
916
917
  )
917
- access_level: Literal["view", "edit"] = Field(
918
+ access_level: OpenValueSet[Literal["view", "edit"]] = Field(
918
919
  ...,
919
920
  title="Access Level",
920
921
  description="Permission level represented by this access-state payload.",
@@ -10,6 +10,7 @@ from pydantic import ConfigDict, Field
10
10
  from .base import BasePydanticModel
11
11
  from .exceptions import ApiError, raise_for_response
12
12
  from .utils import make_request
13
+ from .value_sets import OpenValueSet
13
14
 
14
15
  PublicLogLevel = Literal[
15
16
  "debug",
@@ -55,7 +56,7 @@ class OwnerLogRow(BasePydanticModel):
55
56
  time: int | float | None = None
56
57
  timestamp: str | None = None
57
58
  severity: str | None = None
58
- level: PublicLogLevel | None = None
59
+ level: OpenValueSet[PublicLogLevel] | None = None
59
60
  source: str | None = None
60
61
  event: str | None = None
61
62
  event_id: str | None = None
@@ -110,16 +111,18 @@ class OwnerLogPage(BasePydanticModel):
110
111
  class EnvironmentLogSearchRow(OwnerLogRow):
111
112
  """One sanitized row returned by an Environment-scoped collection search."""
112
113
 
113
- owner_type: Literal[
114
- "deployment_run",
115
- "job_run",
116
- "resource_release",
117
- "agent",
118
- "agent_session",
114
+ owner_type: OpenValueSet[
115
+ Literal[
116
+ "deployment_run",
117
+ "job_run",
118
+ "resource_release",
119
+ "agent",
120
+ "agent_session",
121
+ ]
119
122
  ]
120
123
  owner_uid: str
121
124
  occurred_at: datetime.datetime
122
- level: PublicLogLevel
125
+ level: OpenValueSet[PublicLogLevel]
123
126
 
124
127
 
125
128
  class EnvironmentLogSearchPage(BasePydanticModel):
@@ -134,7 +137,9 @@ class EnvironmentLogSearchPage(BasePydanticModel):
134
137
  result_limit: int = Field(ge=0)
135
138
  next_cursor: str | None = None
136
139
  truncated: bool
137
- truncation_reason: Literal["page_limit", "result_limit", "candidate_limit"] | None = None
140
+ truncation_reason: (
141
+ OpenValueSet[Literal["page_limit", "result_limit", "candidate_limit"]] | None
142
+ ) = None
138
143
  rows: list[EnvironmentLogSearchRow] = Field(default_factory=list)
139
144
 
140
145
 
@@ -0,0 +1,164 @@
1
+ """Open value sets: read a closed vocabulary without failing on an undeclared value.
2
+
3
+ ADR 0033. A response field whose vocabulary the backend is allowed to extend — a
4
+ state, a phase, a kind — is declared with the types here instead of a bare `Enum`
5
+ or `Literal`. A value this SDK release does not declare is kept as the backend
6
+ wrote it and reported once, so one new backend state does not fail the row that
7
+ carries it, nor the listing that row is in.
8
+
9
+ Values the SDK *sends* stay closed. A request parameter typed `Literal[...]` still
10
+ rejects a value the backend does not accept, because there the mistake is the
11
+ caller's and raising is what catches it.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from enum import Enum
17
+ from typing import Annotated, Any, Literal, get_args, get_origin
18
+
19
+ from pydantic import AfterValidator
20
+
21
+ from mainsequence.logconf import logger
22
+
23
+ # An unknown value is reported once per process, not once per row: a listing of
24
+ # 500 rows carrying the same new state is one backend change, so it is one line.
25
+ # The set is capped so a response cannot grow it without bound.
26
+ _MAX_REPORTED_UNKNOWN_VALUES = 512
27
+ _reported_unknown_values: set[tuple[str, str, str]] = set()
28
+
29
+
30
+ def _report_unknown_value(
31
+ owner: str,
32
+ field_name: str | None,
33
+ value: str,
34
+ known: tuple[str, ...],
35
+ ) -> None:
36
+ """Log an undeclared value once, naming the model, the field and the value."""
37
+ key = (owner, field_name or "", value)
38
+ if key in _reported_unknown_values:
39
+ return
40
+ if len(_reported_unknown_values) >= _MAX_REPORTED_UNKNOWN_VALUES:
41
+ return
42
+ _reported_unknown_values.add(key)
43
+ where = f"{owner}.{field_name}" if field_name else owner
44
+ logger.warning(
45
+ f"{where} received {value!r}, which this mainsequence release does not declare. "
46
+ f"The value is kept as sent. Declared values: {', '.join(known)}. "
47
+ f"Upgrade the SDK to read it as a declared member."
48
+ )
49
+
50
+
51
+ def _reset_reported_unknown_values() -> None:
52
+ """Forget what has been reported. For tests that assert on the log line."""
53
+ _reported_unknown_values.clear()
54
+
55
+
56
+ class OpenStrEnum(str, Enum):
57
+ """A string enum that keeps a value it does not declare instead of raising.
58
+
59
+ A known value validates to the declared member, so `kind is Kind.AGENT` and
60
+ `kind == "agent"` both keep working. An undeclared value becomes a member-like
61
+ object carrying that exact string: it compares equal to the string, serializes
62
+ as the string, and is not equal to any declared member. It is deliberately not
63
+ registered on the class, so iterating the enum still lists only what this
64
+ release declares.
65
+ """
66
+
67
+ @classmethod
68
+ def _missing_(cls, value: object) -> OpenStrEnum | None:
69
+ if not isinstance(value, str):
70
+ # Not a string: let Enum raise its own error, as before.
71
+ return None
72
+ undeclared = str.__new__(cls, value)
73
+ undeclared._name_ = value
74
+ undeclared._value_ = value
75
+ _report_unknown_value(
76
+ cls.__name__,
77
+ None,
78
+ value,
79
+ tuple(member.value for member in cls),
80
+ )
81
+ return undeclared
82
+
83
+
84
+ class _KeepUndeclaredValue:
85
+ """Validator behind `OpenValueSet`: keep any string, report the undeclared ones."""
86
+
87
+ __slots__ = ("known",)
88
+
89
+ def __init__(self, known: tuple[str, ...]) -> None:
90
+ self.known = known
91
+
92
+ def __call__(self, value: str, info: Any) -> str:
93
+ if value in self.known:
94
+ return value
95
+ owner = (info.config or {}).get("title") or "response"
96
+ _report_unknown_value(owner, info.field_name or "value", value, self.known)
97
+ return value
98
+
99
+ def __eq__(self, other: object) -> bool:
100
+ return isinstance(other, _KeepUndeclaredValue) and other.known == self.known
101
+
102
+ def __hash__(self) -> int:
103
+ return hash((type(self).__name__, self.known))
104
+
105
+
106
+ def _declared_values(item: object) -> tuple[str, ...]:
107
+ """The values the wrapped `Literal[...]` declares."""
108
+ candidates = item if isinstance(item, tuple) else (item,)
109
+ values: list[str] = []
110
+ for candidate in candidates:
111
+ if get_origin(candidate) is not Literal:
112
+ raise TypeError(
113
+ "OpenValueSet wraps a Literal, e.g. OpenValueSet[Literal['a', 'b']] "
114
+ f"or OpenValueSet[PublicLogLevel]; got {candidate!r}"
115
+ )
116
+ values.extend(get_args(candidate))
117
+ if not values:
118
+ raise TypeError("OpenValueSet requires at least one declared value")
119
+ for value in values:
120
+ if not isinstance(value, str):
121
+ raise TypeError(f"OpenValueSet takes string values; got {value!r}")
122
+ return tuple(dict.fromkeys(values))
123
+
124
+
125
+ class OpenValueSet:
126
+ """Wraps a `Literal[...]` for a response field whose vocabulary may grow.
127
+
128
+ Reads as a `str` that keeps an undeclared value instead of raising:
129
+
130
+ state: OpenValueSet[Literal["routable", "unavailable"]]
131
+ level: OpenValueSet[PublicLogLevel]
132
+
133
+ The values stay inside a real `Literal`, so they keep one spelling for readers,
134
+ for `Literal` aliases reused between a request parameter and a response field,
135
+ and for tools that rewrite annotations. `declared_values()` reads them back.
136
+ """
137
+
138
+ def __class_getitem__(cls, item: object) -> Any:
139
+ known = _declared_values(item)
140
+ return Annotated[str, AfterValidator(_KeepUndeclaredValue(known))]
141
+
142
+
143
+ def declared_values(annotation: Any) -> tuple[str, ...]:
144
+ """The values an `OpenValueSet` declares, in declaration order.
145
+
146
+ Takes the annotation itself, or the `FieldInfo` pydantic built from it —
147
+ `Model.model_fields["state"]` — because pydantic moves the metadata there.
148
+ """
149
+ candidates = (
150
+ *getattr(annotation, "__metadata__", ()),
151
+ *getattr(annotation, "metadata", ()),
152
+ )
153
+ for meta in candidates:
154
+ func = getattr(meta, "func", None)
155
+ if isinstance(func, _KeepUndeclaredValue):
156
+ return func.known
157
+ raise TypeError(f"{annotation!r} does not declare an OpenValueSet")
158
+
159
+
160
+ __all__ = [
161
+ "OpenStrEnum",
162
+ "OpenValueSet",
163
+ "declared_values",
164
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.20.dev14
3
+ Version: 8.1.20.dev16
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -36,24 +36,25 @@ mainsequence/cli/sdk_utils.py,sha256=AuhnDkQ8QsBGSW7l7yqmQvaDAzfC-0iEVvFNkm3sJ48
36
36
  mainsequence/cli/ssh_utils.py,sha256=cucQFeQoctMPhoghCyrrb04WR6DhRBQXAOSZ3jFQwow,13552
37
37
  mainsequence/cli/ui.py,sha256=ATgpcIhzZ1CXGwPcvEWOrJ3OEWUIAHK-mRTuKJ0uzzc,3454
38
38
  mainsequence/client/__init__.py,sha256=Xl2ie3HEO00V1C2xurxbhlWp_nXyV4SZMkeJXh-zZ1Q,548
39
- mainsequence/client/agent_runtime_models.py,sha256=ZFWuZaR1Q1_25M-lamIKQtLJI3uikQsp3g3O9lxOORk,66622
39
+ mainsequence/client/agent_runtime_models.py,sha256=B3QFA-dE7X-98bFY_HeVxVBpnI583q121n3EhZUcC7g,66948
40
40
  mainsequence/client/base.py,sha256=Xg9TXBx7IXglSWX-aRYtOMbftkXioBOZxFNwcHF5TF8,42730
41
41
  mainsequence/client/client.py,sha256=sUsJhjwKq4oZsEX2Ozm-7owcyQq0kgFNKUrtEYwf8cU,3907
42
42
  mainsequence/client/compute_validation.py,sha256=ByeX0CkIE7qq06ggZFfN92U3Qp2e9cvo2RTIGWa5_Vw,6662
43
43
  mainsequence/client/dtype_codec.py,sha256=0ydXPw0dWTVtUzmXoT6ilr2TM_vNC3WTiy3I8QI1ni8,16522
44
44
  mainsequence/client/exceptions.py,sha256=xx2L4l3OMNsTybbYkiTKK398ahnutEascM0It35tGr0,3815
45
- mainsequence/client/github_issues.py,sha256=1jNUJD_NZx25K0naUC23CUx6_Nm8kpriXc0DFjjqGKE,14477
46
- mainsequence/client/models_foundry.py,sha256=ODPdpFQ334hmD2wd_V0KoU9og6yk01nfHIJ24oWySfU,44872
47
- mainsequence/client/models_helpers.py,sha256=a2sQER4o75zmU4JxxuX-rBypr5M0-wECbX7gXsp00SQ,65343
48
- mainsequence/client/models_user.py,sha256=MYVQgpNFuodrNfDuNgO23Q6JZCpsiNykiBAwo3jGPUE,41238
49
- mainsequence/client/observability.py,sha256=onRFc5G3Y6omlkj86hTsPLV2q2NBd5uqtyu4YuH_GtY,15695
45
+ mainsequence/client/github_issues.py,sha256=pSTUw3UPrmE6wqnpoJctartTaNSq6iu6PQRrd8hip6I,14568
46
+ mainsequence/client/models_foundry.py,sha256=jrEXZrx3WweleogeDwjSjQOjQN5p3j-Ox9YDXa2iW5g,44937
47
+ mainsequence/client/models_helpers.py,sha256=5-0ypOf_Dkp4rVmRR8S2TQ8_wb0ABjoc7oY-b6oatD4,66363
48
+ mainsequence/client/models_user.py,sha256=l7t7q1nSa2kbLZ7dHbAjSpuRY8e9kWEjC8u3MK4Qfzw,41317
49
+ mainsequence/client/observability.py,sha256=tDoy2NDcDrjeXFAHuxaylaMy2-NOuoN_arFdIOMppro,15842
50
50
  mainsequence/client/utils.py,sha256=vaku-aeUpnn220nzEVcSQWdiykSeW3sUzgDtYter5tk,28784
51
+ mainsequence/client/value_sets.py,sha256=BYTvMyWGqDJu5wlIADsLoGymFoYBeQmXcW_dDRFnF6U,6114
51
52
  mainsequence/client/data_sources_interfaces/__init__.py,sha256=oUnKUx0D4nbNXYgVMgvac6MfgVNKuzGZL045p4VKqUg,668
52
53
  mainsequence/client/data_sources_interfaces/duckdb.py,sha256=JZHsN3fB2UQYpNQJnp2pAqG_9Q6FX8DIM_c8A6GbPSc,63383
53
54
  mainsequence/client/data_sources_interfaces/local_paths.py,sha256=aO7-pTGBWItcpY1MxoYYP0mbEgB3dDWqDOwniezNw5c,323
54
55
  mainsequence/client/data_sources_interfaces/sqlite.py,sha256=gWpuG9FbPWF69urRqU_K5yWX7vkHGPeOCYaJwVu8nG0,16392
55
56
  mainsequence/client/metatables/__init__.py,sha256=dLxgfvV8t5kb59SxBowXxLJJQAc1BPgNzVUl_7U6SeA,580
56
- mainsequence/client/metatables/core.py,sha256=FdYKoAU6AjIS3lFAOXT6GOJTgJvD01HFea9yb6-H1DM,185916
57
+ mainsequence/client/metatables/core.py,sha256=QxtY8-pu_smlTjQGV00b-PzFz1leRLIoblnbRDDjECQ,186480
57
58
  mainsequence/instrumentation/__init__.py,sha256=K1W1vx2wu1QldzUD6tI4C8op9mFcVpS7D-Ina1O9TFc,152
58
59
  mainsequence/instrumentation/utils.py,sha256=b-ihjSioAepeLfMpudM2SqCtvnxMqIXileHx3klBCQU,3377
59
60
  mainsequence/meta_tables/__init__.py,sha256=aHwu6kRSunS3-ZiVnSS-8rBH09ZnWUu82hgrmnpeEf4,5267
@@ -83,9 +84,9 @@ mainsequence/meta_tables/time_index_table_updates/namespacing.py,sha256=obJfMnjL
83
84
  mainsequence/meta_tables/time_index_table_updates/runner.py,sha256=853p_iXJ835pI8pX3519HSdTw93Bqqd3JxHOfm3S6yg,34412
84
85
  mainsequence/meta_tables/time_index_table_updates/updaters.py,sha256=-6zJgi1sDRCyIyVxLwTfb2RLLZS4LC0syNJHnJRAtgU,31404
85
86
  mainsequence/meta_tables/time_index_table_updates/utils.py,sha256=O1iKp2tzCkqxyCblI6MKxez6XV180w87VszxDO9OfAE,621
86
- mainsequence-8.1.20.dev14.dist-info/licenses/LICENSE,sha256=fXoCKgEuZXmP84_QDXpvO18QHze_6AAhd-zvZBUjJxQ,4524
87
- mainsequence-8.1.20.dev14.dist-info/METADATA,sha256=Fu0seRII3KQmUv5_7T7kUqqxdygkAARPlhdsXLPNNV0,7384
88
- mainsequence-8.1.20.dev14.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
89
- mainsequence-8.1.20.dev14.dist-info/entry_points.txt,sha256=XGAOq3-mF2lWWpeRJvoaWyMarRcK167mH1g124aGdUs,88
90
- mainsequence-8.1.20.dev14.dist-info/top_level.txt,sha256=BFeblM_Q9q0cUWBvTxnfWcWQEHtf5XZi9nOS-Gdfwus,28
91
- mainsequence-8.1.20.dev14.dist-info/RECORD,,
87
+ mainsequence-8.1.20.dev16.dist-info/licenses/LICENSE,sha256=fXoCKgEuZXmP84_QDXpvO18QHze_6AAhd-zvZBUjJxQ,4524
88
+ mainsequence-8.1.20.dev16.dist-info/METADATA,sha256=DoNMwGRr6-fOmywBWA-MhO7Zc7IsndZsVV0r1Ak91sk,7384
89
+ mainsequence-8.1.20.dev16.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
90
+ mainsequence-8.1.20.dev16.dist-info/entry_points.txt,sha256=XGAOq3-mF2lWWpeRJvoaWyMarRcK167mH1g124aGdUs,88
91
+ mainsequence-8.1.20.dev16.dist-info/top_level.txt,sha256=BFeblM_Q9q0cUWBvTxnfWcWQEHtf5XZi9nOS-Gdfwus,28
92
+ mainsequence-8.1.20.dev16.dist-info/RECORD,,