dirigent-client 0.9.0__tar.gz

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 (35) hide show
  1. dirigent_client-0.9.0/LICENSE +18 -0
  2. dirigent_client-0.9.0/PKG-INFO +36 -0
  3. dirigent_client-0.9.0/README.md +23 -0
  4. dirigent_client-0.9.0/pyproject.toml +20 -0
  5. dirigent_client-0.9.0/pyproject.toml.orig +20 -0
  6. dirigent_client-0.9.0/src/dirigent_client/__init__.py +219 -0
  7. dirigent_client-0.9.0/src/dirigent_client/client.py +143 -0
  8. dirigent_client-0.9.0/src/dirigent_client/enums.py +215 -0
  9. dirigent_client-0.9.0/src/dirigent_client/errors.py +140 -0
  10. dirigent_client-0.9.0/src/dirigent_client/py.typed +0 -0
  11. dirigent_client-0.9.0/src/dirigent_client/resources/__init__.py +1 -0
  12. dirigent_client-0.9.0/src/dirigent_client/resources/alerts.py +97 -0
  13. dirigent_client-0.9.0/src/dirigent_client/resources/auth.py +155 -0
  14. dirigent_client-0.9.0/src/dirigent_client/resources/base.py +52 -0
  15. dirigent_client-0.9.0/src/dirigent_client/resources/blocks.py +16 -0
  16. dirigent_client-0.9.0/src/dirigent_client/resources/connections.py +50 -0
  17. dirigent_client-0.9.0/src/dirigent_client/resources/pipelines.py +209 -0
  18. dirigent_client-0.9.0/src/dirigent_client/resources/runs.py +257 -0
  19. dirigent_client-0.9.0/src/dirigent_client/resources/schedules.py +130 -0
  20. dirigent_client-0.9.0/src/dirigent_client/resources/schemas.py +45 -0
  21. dirigent_client-0.9.0/src/dirigent_client/resources/system.py +36 -0
  22. dirigent_client-0.9.0/src/dirigent_client/resources/trigger_documents.py +20 -0
  23. dirigent_client-0.9.0/src/dirigent_client/resources/webhooks.py +80 -0
  24. dirigent_client-0.9.0/src/dirigent_client/schemas/__init__.py +164 -0
  25. dirigent_client-0.9.0/src/dirigent_client/schemas/alerts.py +90 -0
  26. dirigent_client-0.9.0/src/dirigent_client/schemas/auth.py +115 -0
  27. dirigent_client-0.9.0/src/dirigent_client/schemas/catalog.py +66 -0
  28. dirigent_client-0.9.0/src/dirigent_client/schemas/common.py +38 -0
  29. dirigent_client-0.9.0/src/dirigent_client/schemas/connections.py +56 -0
  30. dirigent_client-0.9.0/src/dirigent_client/schemas/pipelines.py +326 -0
  31. dirigent_client-0.9.0/src/dirigent_client/schemas/runs.py +203 -0
  32. dirigent_client-0.9.0/src/dirigent_client/schemas/schemas.py +54 -0
  33. dirigent_client-0.9.0/src/dirigent_client/schemas/system.py +100 -0
  34. dirigent_client-0.9.0/src/dirigent_client/schemas/triggers.py +208 -0
  35. dirigent_client-0.9.0/src/dirigent_client/transport.py +191 -0
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2026 Morten Olav Hansen <morten@winterop.com>. All rights reserved.
2
+
3
+ This source code and accompanying documentation are the property of
4
+ Morten Olav Hansen. No license, express or implied, is granted to use, copy,
5
+ modify, merge, publish, distribute, sublicense, or sell copies of this
6
+ software or its derivatives.
7
+
8
+ The source is published for reference only. Any use beyond reading
9
+ requires written permission from the copyright holder.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
12
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
14
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES,
15
+ OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
16
+
17
+ Third-party components redistributed with this software, and the licences they
18
+ carry, are listed in THIRD_PARTY_NOTICES.md.
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: dirigent-client
3
+ Version: 0.9.0
4
+ Summary: The dirigent API contract: typed wire schemas and an async Python client.
5
+ License-Expression: LicenseRef-Proprietary
6
+ License-File: LICENSE
7
+ Requires-Dist: dirigent-common
8
+ Requires-Dist: httpx2>=2.12.0
9
+ Requires-Dist: pydantic>=2.13.5
10
+ Requires-Dist: pyyaml>=6.0.3
11
+ Requires-Python: >=3.13
12
+ Description-Content-Type: text/markdown
13
+
14
+ # dirigent-client
15
+
16
+ The dirigent API contract: typed wire schemas and an async Python client.
17
+
18
+ ```python
19
+ from datetime import timedelta
20
+
21
+ from dirigent_client import Dirigent
22
+
23
+ async with Dirigent(url="https://dirigent.example.org", token=token) as dg:
24
+ plan = await dg.pipelines.apply("pipelines/daily-climate-load.yaml", dry_run=True)
25
+ run = await dg.pipelines.run("daily-climate-load", params={"day": "2026-08-29"})
26
+ final = await dg.runs.wait(run.run_id, timeout=timedelta(hours=2))
27
+ async for entry in dg.runs.follow_logs(run.run_id):
28
+ print(entry.message)
29
+ ```
30
+
31
+ The package holds the pydantic schemas for every request and response the REST API speaks,
32
+ and `dirigent-server` imports them, so the client and the server can never disagree about a
33
+ shape. It depends on `dirigent-common`, `httpx2`, `pydantic`, and `pyyaml`, and on nothing else in the
34
+ workspace.
35
+
36
+ `docs/python.md` has the worked examples, and `examples/python/` has six runnable scripts.
@@ -0,0 +1,23 @@
1
+ # dirigent-client
2
+
3
+ The dirigent API contract: typed wire schemas and an async Python client.
4
+
5
+ ```python
6
+ from datetime import timedelta
7
+
8
+ from dirigent_client import Dirigent
9
+
10
+ async with Dirigent(url="https://dirigent.example.org", token=token) as dg:
11
+ plan = await dg.pipelines.apply("pipelines/daily-climate-load.yaml", dry_run=True)
12
+ run = await dg.pipelines.run("daily-climate-load", params={"day": "2026-08-29"})
13
+ final = await dg.runs.wait(run.run_id, timeout=timedelta(hours=2))
14
+ async for entry in dg.runs.follow_logs(run.run_id):
15
+ print(entry.message)
16
+ ```
17
+
18
+ The package holds the pydantic schemas for every request and response the REST API speaks,
19
+ and `dirigent-server` imports them, so the client and the server can never disagree about a
20
+ shape. It depends on `dirigent-common`, `httpx2`, `pydantic`, and `pyyaml`, and on nothing else in the
21
+ workspace.
22
+
23
+ `docs/python.md` has the worked examples, and `examples/python/` has six runnable scripts.
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "dirigent-client"
3
+ version = "0.9.0"
4
+ description = "The dirigent API contract: typed wire schemas and an async Python client."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "LicenseRef-Proprietary"
8
+ license-files = ["LICENSE"]
9
+ dependencies = [
10
+ "dirigent-common",
11
+ "httpx2>=2.12.0",
12
+ "pydantic>=2.13.5",
13
+ "pyyaml>=6.0.3",
14
+ ]
15
+
16
+ [build-system]
17
+ requires = ["uv_build>=0.12.0,<0.13.0"]
18
+ build-backend = "uv_build"
19
+
20
+ [tool.uv.sources]
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "dirigent-client"
3
+ version = "0.9.0"
4
+ description = "The dirigent API contract: typed wire schemas and an async Python client."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "LicenseRef-Proprietary"
8
+ license-files = ["LICENSE"]
9
+ dependencies = [
10
+ "dirigent-common",
11
+ "httpx2>=2.12.0",
12
+ "pydantic>=2.13.5",
13
+ "pyyaml>=6.0.3",
14
+ ]
15
+
16
+ [build-system]
17
+ requires = ["uv_build>=0.12.0,<0.13.0"]
18
+ build-backend = "uv_build"
19
+
20
+ [tool.uv.sources]
@@ -0,0 +1,219 @@
1
+ """The dirigent API contract: typed wire schemas, and an async client over them."""
2
+
3
+ from dirigent_client.client import BlockingDirigent, Dirigent
4
+ from dirigent_client.enums import (
5
+ AlertEvent,
6
+ AlertScope,
7
+ AttemptKind,
8
+ AttemptStatus,
9
+ DocumentKind,
10
+ FiringOutcome,
11
+ LogLevel,
12
+ NotificationStatus,
13
+ ProvenanceSource,
14
+ RunItemStatus,
15
+ RunPriority,
16
+ RunStatus,
17
+ ScheduleKind,
18
+ TokenKind,
19
+ TriggerKind,
20
+ UserRole,
21
+ WebhookOutcome,
22
+ WorkerStatus,
23
+ )
24
+ from dirigent_client.errors import (
25
+ VERSION_HEADER,
26
+ Conflict,
27
+ DirigentError,
28
+ Forbidden,
29
+ NotDirigent,
30
+ NotFound,
31
+ RateLimited,
32
+ ServerError,
33
+ TransportError,
34
+ Unauthorized,
35
+ ValidationFailed,
36
+ WaitTimeout,
37
+ )
38
+ from dirigent_client.schemas import (
39
+ TERMINAL_RUN_STATUSES,
40
+ AlertRuleIn,
41
+ AlertRuleOut,
42
+ ApplyRequest,
43
+ ApplyResult,
44
+ AttemptEvent,
45
+ AttemptOut,
46
+ BackfillAccepted,
47
+ BackfilledRun,
48
+ BackfillRequest,
49
+ BlockEntry,
50
+ BlockKind,
51
+ Catalog,
52
+ CheckResult,
53
+ CheckStatus,
54
+ ConnectionHealth,
55
+ ConnectionIn,
56
+ ConnectionOut,
57
+ ConnectionUpdate,
58
+ DagNode,
59
+ DagView,
60
+ DeliveryOut,
61
+ DiffSummary,
62
+ FiringOut,
63
+ Health,
64
+ HookAccepted,
65
+ Identity,
66
+ IssuedTokenOut,
67
+ ItemOut,
68
+ JsonList,
69
+ JsonMap,
70
+ LogEntryOut,
71
+ LoginRequest,
72
+ Materialized,
73
+ NotificationOut,
74
+ Page,
75
+ PipelineDetail,
76
+ PipelineOut,
77
+ PipelinePlan,
78
+ PipelineVersionOut,
79
+ PlanAction,
80
+ Problem,
81
+ Readiness,
82
+ RunAccepted,
83
+ RunDetail,
84
+ RunOut,
85
+ RunReport,
86
+ RunRequest,
87
+ ScheduleIn,
88
+ ScheduleOut,
89
+ SchedulePreview,
90
+ SchedulePreviewRequest,
91
+ SchemaIn,
92
+ SchemaOut,
93
+ SchemaUpdate,
94
+ StepReport,
95
+ SurfaceEntry,
96
+ SystemInfo,
97
+ TestQueued,
98
+ TestRequest,
99
+ TokenOut,
100
+ TokenRequest,
101
+ TriggerDocumentDetail,
102
+ TriggerDocumentOut,
103
+ UserIn,
104
+ UserOut,
105
+ ValidationIssue,
106
+ WebhookIn,
107
+ WebhookOut,
108
+ WebhookTokenOut,
109
+ WorkerOut,
110
+ )
111
+ from dirigent_client.transport import API_PREFIX, Transport
112
+
113
+ __all__ = [
114
+ "API_PREFIX",
115
+ "TERMINAL_RUN_STATUSES",
116
+ "VERSION_HEADER",
117
+ "AlertEvent",
118
+ "AlertRuleIn",
119
+ "AlertRuleOut",
120
+ "AlertScope",
121
+ "ApplyRequest",
122
+ "ApplyResult",
123
+ "BackfillAccepted",
124
+ "BackfillRequest",
125
+ "BackfilledRun",
126
+ "AttemptKind",
127
+ "AttemptEvent",
128
+ "AttemptOut",
129
+ "AttemptStatus",
130
+ "BlockEntry",
131
+ "BlockKind",
132
+ "BlockingDirigent",
133
+ "Catalog",
134
+ "CheckResult",
135
+ "CheckStatus",
136
+ "Conflict",
137
+ "ConnectionHealth",
138
+ "ConnectionIn",
139
+ "ConnectionOut",
140
+ "ConnectionUpdate",
141
+ "SchemaIn",
142
+ "SchemaOut",
143
+ "SchemaUpdate",
144
+ "DagNode",
145
+ "DagView",
146
+ "DocumentKind",
147
+ "DeliveryOut",
148
+ "DiffSummary",
149
+ "Dirigent",
150
+ "DirigentError",
151
+ "FiringOut",
152
+ "FiringOutcome",
153
+ "Forbidden",
154
+ "Health",
155
+ "HookAccepted",
156
+ "Identity",
157
+ "IssuedTokenOut",
158
+ "ItemOut",
159
+ "JsonList",
160
+ "JsonMap",
161
+ "LogEntryOut",
162
+ "LogLevel",
163
+ "LoginRequest",
164
+ "Materialized",
165
+ "NotDirigent",
166
+ "NotFound",
167
+ "NotificationOut",
168
+ "NotificationStatus",
169
+ "Page",
170
+ "PipelineDetail",
171
+ "PipelineOut",
172
+ "PipelinePlan",
173
+ "PipelineVersionOut",
174
+ "PlanAction",
175
+ "Problem",
176
+ "ProvenanceSource",
177
+ "RateLimited",
178
+ "Readiness",
179
+ "RunAccepted",
180
+ "RunDetail",
181
+ "RunItemStatus",
182
+ "RunOut",
183
+ "RunPriority",
184
+ "RunReport",
185
+ "RunRequest",
186
+ "RunStatus",
187
+ "ScheduleIn",
188
+ "ScheduleKind",
189
+ "ScheduleOut",
190
+ "SchedulePreview",
191
+ "SchedulePreviewRequest",
192
+ "ServerError",
193
+ "StepReport",
194
+ "SurfaceEntry",
195
+ "SystemInfo",
196
+ "TestQueued",
197
+ "TestRequest",
198
+ "TokenKind",
199
+ "TokenOut",
200
+ "TokenRequest",
201
+ "TriggerDocumentDetail",
202
+ "TriggerDocumentOut",
203
+ "Transport",
204
+ "TransportError",
205
+ "TriggerKind",
206
+ "Unauthorized",
207
+ "UserIn",
208
+ "UserOut",
209
+ "UserRole",
210
+ "ValidationFailed",
211
+ "ValidationIssue",
212
+ "WaitTimeout",
213
+ "WebhookIn",
214
+ "WebhookOut",
215
+ "WebhookOutcome",
216
+ "WebhookTokenOut",
217
+ "WorkerOut",
218
+ "WorkerStatus",
219
+ ]
@@ -0,0 +1,143 @@
1
+ """The client itself: one connection, and one namespaced accessor per API resource."""
2
+
3
+ import asyncio
4
+ import threading
5
+ from collections.abc import AsyncIterator, Awaitable, Iterator, Mapping
6
+ from typing import Any, Self
7
+
8
+ import httpx2
9
+
10
+ from dirigent_client.resources.alerts import Alerts
11
+ from dirigent_client.resources.auth import Admin, Auth
12
+ from dirigent_client.resources.blocks import Blocks
13
+ from dirigent_client.resources.connections import Connections
14
+ from dirigent_client.resources.pipelines import Pipelines
15
+ from dirigent_client.resources.runs import Runs
16
+ from dirigent_client.resources.schedules import Schedules
17
+ from dirigent_client.resources.schemas import Schemas
18
+ from dirigent_client.resources.system import System, Workers
19
+ from dirigent_client.resources.trigger_documents import TriggerDocuments
20
+ from dirigent_client.resources.webhooks import Webhooks
21
+ from dirigent_client.transport import API_PREFIX, DEFAULT_RETRIES, DEFAULT_TIMEOUT, Transport
22
+
23
+ SHUTDOWN_SECONDS = 5.0
24
+
25
+
26
+ class Dirigent:
27
+ """An authenticated connection to one dirigent instance."""
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ url: str,
33
+ token: str | None = None,
34
+ timeout: float = DEFAULT_TIMEOUT,
35
+ retries: int = DEFAULT_RETRIES,
36
+ headers: Mapping[str, str] | None = None,
37
+ http_transport: httpx2.AsyncBaseTransport | None = None,
38
+ api_prefix: str = API_PREFIX,
39
+ ) -> None:
40
+ """Open a connection to an instance, with a bearer token when one is needed."""
41
+ self.transport = Transport(
42
+ url=url,
43
+ api_prefix=api_prefix,
44
+ token=token,
45
+ timeout=timeout,
46
+ retries=retries,
47
+ headers=headers,
48
+ http_transport=http_transport,
49
+ )
50
+ self.pipelines = Pipelines(self.transport)
51
+ self.runs = Runs(self.transport)
52
+ self.connections = Connections(self.transport)
53
+ self.schemas = Schemas(self.transport)
54
+ self.blocks = Blocks(self.transport)
55
+ self.schedules = Schedules(self.transport)
56
+ self.webhooks = Webhooks(self.transport)
57
+ self.trigger_documents = TriggerDocuments(self.transport)
58
+ self.alerts = Alerts(self.transport)
59
+ self.system = System(self.transport)
60
+ self.workers = Workers(self.transport)
61
+ self.auth = Auth(self.transport)
62
+ self.admin = Admin(self.transport)
63
+
64
+ @property
65
+ def url(self) -> str:
66
+ """Name the instance this client is bound to."""
67
+ return self.transport.url
68
+
69
+ async def __aenter__(self) -> Self:
70
+ """Enter the client's context."""
71
+ return self
72
+
73
+ async def __aexit__(self, *_: object) -> None:
74
+ """Close the underlying connection pool."""
75
+ await self.aclose()
76
+
77
+ async def aclose(self) -> None:
78
+ """Close the underlying connection pool."""
79
+ await self.transport.aclose()
80
+
81
+
82
+ class BlockingDirigent:
83
+ """The same client, driven from synchronous code."""
84
+
85
+ def __init__(self, **kwargs: Any) -> None:
86
+ """Open a connection and the loop its calls run on; the arguments are Dirigent's."""
87
+ self._loop = asyncio.new_event_loop()
88
+ self._thread = threading.Thread(target=self._loop.run_forever, name="dirigent-client", daemon=True)
89
+ self._thread.start()
90
+ self.client = Dirigent(**kwargs)
91
+ self.url = self.client.url
92
+ self.pipelines = self.client.pipelines
93
+ self.runs = self.client.runs
94
+ self.connections = self.client.connections
95
+ self.schemas = self.client.schemas
96
+ self.blocks = self.client.blocks
97
+ self.schedules = self.client.schedules
98
+ self.webhooks = self.client.webhooks
99
+ self.trigger_documents = self.client.trigger_documents
100
+ self.alerts = self.client.alerts
101
+ self.system = self.client.system
102
+ self.workers = self.client.workers
103
+ self.auth = self.client.auth
104
+ self.admin = self.client.admin
105
+
106
+ def __enter__(self) -> Self:
107
+ """Enter the client's context."""
108
+ return self
109
+
110
+ def __exit__(self, *_: object) -> None:
111
+ """Close the connection pool and stop the loop."""
112
+ self.close()
113
+
114
+ def call[T](self, awaitable: Awaitable[T]) -> T:
115
+ """Run one of the client's coroutines to completion and return what it produced."""
116
+ return asyncio.run_coroutine_threadsafe(_awaited(awaitable), self._loop).result()
117
+
118
+ def iterate[T](self, source: AsyncIterator[T]) -> Iterator[T]:
119
+ """Read an async iterator, such as a log tail, as an ordinary one."""
120
+ while True:
121
+ try:
122
+ yield self.call(anext(source))
123
+ except StopAsyncIteration:
124
+ return
125
+
126
+ def close(self) -> None:
127
+ """Close the connection pool and stop the loop it was running on.
128
+
129
+ The asynchronous generators must be shut down before the loop is: a generator
130
+ finalised after its loop has closed raises where nobody is listening.
131
+ """
132
+ try:
133
+ self.call(self.client.aclose())
134
+ self.call(self._loop.shutdown_asyncgens())
135
+ finally:
136
+ self._loop.call_soon_threadsafe(self._loop.stop)
137
+ self._thread.join(timeout=SHUTDOWN_SECONDS)
138
+ self._loop.close()
139
+
140
+
141
+ async def _awaited[T](awaitable: Awaitable[T]) -> T:
142
+ """Wrap an awaitable as the coroutine ``run_coroutine_threadsafe`` requires."""
143
+ return await awaitable
@@ -0,0 +1,215 @@
1
+ """The closed vocabularies the engine's state machines are built from."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class RunStatus(StrEnum):
7
+ """Where a run is."""
8
+
9
+ QUEUED = "queued"
10
+ RUNNING = "running"
11
+ SUCCEEDED = "succeeded"
12
+ COMPLETED_WITH_ERRORS = "completed_with_errors"
13
+ FAILED = "failed"
14
+ CANCELLED = "cancelled"
15
+
16
+
17
+ class AttemptStatus(StrEnum):
18
+ """The step-attempt state machine."""
19
+
20
+ PENDING = "pending"
21
+ """Waiting on its depends_on edges; not yet claimable."""
22
+
23
+ QUEUED = "queued"
24
+ """Claimable once available_at has passed."""
25
+
26
+ RUNNING = "running"
27
+ """Claimed by a worker, holding a lease."""
28
+
29
+ WAITING = "waiting"
30
+ """Not on a worker: parked with a remote handle or a pending poke, re-examined when
31
+ next_poll_at is due."""
32
+
33
+ SUCCEEDED = "succeeded"
34
+ FAILED = "failed"
35
+ SKIPPED = "skipped"
36
+ CANCELLED = "cancelled"
37
+
38
+
39
+ class AttemptKind(StrEnum):
40
+ """Whether the engine created an attempt or an operator asked for it."""
41
+
42
+ AUTOMATIC = "automatic"
43
+ MANUAL = "manual"
44
+
45
+
46
+ class RunItemStatus(StrEnum):
47
+ """Per-item status of a fan-out step."""
48
+
49
+ PENDING = "pending"
50
+ RUNNING = "running"
51
+ SUCCEEDED = "succeeded"
52
+ FAILED = "failed"
53
+ SKIPPED = "skipped"
54
+
55
+
56
+ class RunPriority(StrEnum):
57
+ """How far ahead of the other runs a run's attempts are claimed."""
58
+
59
+ LOW = "low"
60
+ """Claimed after everything else, which is where a bulk job belongs."""
61
+
62
+ NORMAL = "normal"
63
+
64
+ HIGH = "high"
65
+ """Claimed before any other run's attempts, the moment a slot frees."""
66
+
67
+
68
+ #: What each priority is worth to the claim's sort, highest first.
69
+ PRIORITY_RANK: dict[RunPriority, int] = {RunPriority.LOW: 0, RunPriority.NORMAL: 1, RunPriority.HIGH: 2}
70
+
71
+
72
+ class TriggerKind(StrEnum):
73
+ """What started a run."""
74
+
75
+ ADHOC = "adhoc"
76
+ SCHEDULE = "schedule"
77
+ WEBHOOK = "webhook"
78
+ API_TOKEN = "api_token"
79
+ USER = "user"
80
+ PIPELINE = "pipeline"
81
+ """A step of another run started this one, and ``triggered_by_id`` names that run."""
82
+
83
+ BACKFILL = "backfill"
84
+ """A backfill over past windows created this one, and ``triggered_by_id`` names the schedule."""
85
+
86
+
87
+ class ScheduleKind(StrEnum):
88
+ """How a schedule computes its next firing."""
89
+
90
+ CRON = "cron"
91
+ INTERVAL = "interval"
92
+ ONE_TIME = "one_time"
93
+
94
+
95
+ class AlertEvent(StrEnum):
96
+ """The run-level events an alert rule may bind to."""
97
+
98
+ RUN_FAILED = "run_failed"
99
+ RUN_COMPLETED_WITH_ERRORS = "run_completed_with_errors"
100
+ RUN_SUCCEEDED = "run_succeeded"
101
+ RUN_STUCK = "run_stuck"
102
+
103
+
104
+ class AlertScope(StrEnum):
105
+ """Whether an alert rule watches everything or one pipeline."""
106
+
107
+ GLOBAL = "global"
108
+ PIPELINE = "pipeline"
109
+
110
+
111
+ class NotificationStatus(StrEnum):
112
+ """Where one queued alert delivery is."""
113
+
114
+ PENDING = "pending"
115
+ """Queued and claimable once available_at has passed."""
116
+
117
+ SENDING = "sending"
118
+ """Claimed by a worker, holding a lease."""
119
+
120
+ SENT = "sent"
121
+ """The notifier accepted it."""
122
+
123
+ FAILED = "failed"
124
+ """Terminally undeliverable: the retry budget is gone, and the error is on the row."""
125
+
126
+
127
+ class FiringOutcome(StrEnum):
128
+ """What one scheduler tick decided about one due schedule."""
129
+
130
+ FIRED = "fired"
131
+ """A run was created and is executing."""
132
+
133
+ QUEUED = "queued"
134
+ """A run was created but is held behind the one already in flight."""
135
+
136
+ REPLACED = "replaced"
137
+ """The run in flight was cancelled and a new one took its place."""
138
+
139
+ SKIPPED = "skipped"
140
+ """The concurrency policy dropped this firing; the run in flight is enough."""
141
+
142
+ FAILED = "failed"
143
+ """The run could not be created at all, and the reason is on the row."""
144
+
145
+
146
+ class WebhookOutcome(StrEnum):
147
+ """What the intake endpoint did with one delivery."""
148
+
149
+ ACCEPTED = "accepted"
150
+ """The payload mapped, validated, and started a run."""
151
+
152
+ SKIPPED = "skipped"
153
+ """The payload was good, but the pipeline's concurrency policy dropped the run."""
154
+
155
+ REJECTED = "rejected"
156
+ """The delivery was refused, and the reason is on the row."""
157
+
158
+
159
+ class LogLevel(StrEnum):
160
+ """Severity of a product log entry, which is separate from process logging."""
161
+
162
+ DEBUG = "debug"
163
+ INFO = "info"
164
+ WARNING = "warning"
165
+ ERROR = "error"
166
+
167
+
168
+ class DocumentKind(StrEnum):
169
+ """Which kind of ``dirigent/v1`` document an apply carried."""
170
+
171
+ PIPELINE = "pipeline"
172
+ TRIGGERS = "triggers"
173
+ """Clocks and webhooks for a pipeline defined somewhere else."""
174
+
175
+
176
+ class ProvenanceSource(StrEnum):
177
+ """Where a pipeline version came from, recorded when it is applied."""
178
+
179
+ UI = "ui"
180
+ FILE = "file"
181
+ URL = "url"
182
+ API = "api"
183
+ DIRECTORY = "directory"
184
+
185
+
186
+ class UserRole(StrEnum):
187
+ """What an account may do."""
188
+
189
+ ADMIN = "admin"
190
+ """Full access: definitions, connections, runs, users, and tokens."""
191
+
192
+ OPERATOR = "operator"
193
+ """Define, apply, run, cancel and schedule, but not manage accounts, tokens or connections."""
194
+
195
+ VIEWER = "viewer"
196
+ """Read-only: every listing, run, log and report, and nothing that writes."""
197
+
198
+
199
+ class TokenKind(StrEnum):
200
+ """Whether a stored credential is an automation token or a browser session."""
201
+
202
+ API = "api"
203
+ """A bearer token an operator created for a script or a CI job."""
204
+
205
+ SESSION = "session"
206
+ """A cookie-borne session minted by a login, expiring on its own."""
207
+
208
+
209
+ class WorkerStatus(StrEnum):
210
+ """What the worker registry last heard from a node."""
211
+
212
+ STARTING = "starting"
213
+ RUNNING = "running"
214
+ DRAINING = "draining"
215
+ STOPPED = "stopped"