stabilize 0.9.2__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.
- stabilize/__init__.py +29 -0
- stabilize/cli.py +1193 -0
- stabilize/context/__init__.py +7 -0
- stabilize/context/stage_context.py +170 -0
- stabilize/dag/__init__.py +15 -0
- stabilize/dag/graph.py +215 -0
- stabilize/dag/topological.py +199 -0
- stabilize/examples/__init__.py +1 -0
- stabilize/examples/docker-example.py +759 -0
- stabilize/examples/golden-standard-expected-result.txt +1 -0
- stabilize/examples/golden-standard.py +488 -0
- stabilize/examples/http-example.py +606 -0
- stabilize/examples/llama-example.py +662 -0
- stabilize/examples/python-example.py +731 -0
- stabilize/examples/shell-example.py +399 -0
- stabilize/examples/ssh-example.py +603 -0
- stabilize/handlers/__init__.py +53 -0
- stabilize/handlers/base.py +226 -0
- stabilize/handlers/complete_stage.py +209 -0
- stabilize/handlers/complete_task.py +75 -0
- stabilize/handlers/complete_workflow.py +150 -0
- stabilize/handlers/run_task.py +369 -0
- stabilize/handlers/start_stage.py +262 -0
- stabilize/handlers/start_task.py +74 -0
- stabilize/handlers/start_workflow.py +136 -0
- stabilize/launcher.py +307 -0
- stabilize/migrations/01KDQ4N9QPJ6Q4MCV3V9GHWPV4_initial_schema.sql +97 -0
- stabilize/migrations/01KDRK3TXW4R2GERC1WBCQYJGG_rag_embeddings.sql +25 -0
- stabilize/migrations/__init__.py +1 -0
- stabilize/models/__init__.py +15 -0
- stabilize/models/stage.py +389 -0
- stabilize/models/status.py +146 -0
- stabilize/models/task.py +125 -0
- stabilize/models/workflow.py +317 -0
- stabilize/orchestrator.py +113 -0
- stabilize/persistence/__init__.py +28 -0
- stabilize/persistence/connection.py +185 -0
- stabilize/persistence/factory.py +136 -0
- stabilize/persistence/memory.py +214 -0
- stabilize/persistence/postgres.py +655 -0
- stabilize/persistence/sqlite.py +674 -0
- stabilize/persistence/store.py +235 -0
- stabilize/queue/__init__.py +59 -0
- stabilize/queue/messages.py +377 -0
- stabilize/queue/processor.py +312 -0
- stabilize/queue/queue.py +526 -0
- stabilize/queue/sqlite_queue.py +354 -0
- stabilize/rag/__init__.py +19 -0
- stabilize/rag/assistant.py +459 -0
- stabilize/rag/cache.py +294 -0
- stabilize/stages/__init__.py +11 -0
- stabilize/stages/builder.py +253 -0
- stabilize/tasks/__init__.py +19 -0
- stabilize/tasks/interface.py +335 -0
- stabilize/tasks/registry.py +255 -0
- stabilize/tasks/result.py +283 -0
- stabilize-0.9.2.dist-info/METADATA +301 -0
- stabilize-0.9.2.dist-info/RECORD +61 -0
- stabilize-0.9.2.dist-info/WHEEL +4 -0
- stabilize-0.9.2.dist-info/entry_points.txt +2 -0
- stabilize-0.9.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StartWorkflowHandler - handles pipeline execution startup.
|
|
3
|
+
|
|
4
|
+
This handler is triggered when a new pipeline execution is started.
|
|
5
|
+
It finds initial stages (those with no dependencies) and queues them
|
|
6
|
+
for execution.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from stabilize.handlers.base import StabilizeHandler
|
|
15
|
+
from stabilize.models.status import WorkflowStatus
|
|
16
|
+
from stabilize.queue.messages import (
|
|
17
|
+
CancelWorkflow,
|
|
18
|
+
StartStage,
|
|
19
|
+
StartWorkflow,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from stabilize.models.workflow import Workflow
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StartWorkflowHandler(StabilizeHandler[StartWorkflow]):
|
|
29
|
+
"""
|
|
30
|
+
Handler for StartWorkflow messages.
|
|
31
|
+
|
|
32
|
+
When a pipeline execution starts:
|
|
33
|
+
1. Check if execution should be queued (concurrent limits)
|
|
34
|
+
2. Find initial stages (no dependencies)
|
|
35
|
+
3. Push StartStage for each initial stage
|
|
36
|
+
4. Mark execution as RUNNING
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def message_type(self) -> type[StartWorkflow]:
|
|
41
|
+
return StartWorkflow
|
|
42
|
+
|
|
43
|
+
def handle(self, message: StartWorkflow) -> None:
|
|
44
|
+
"""Handle the StartWorkflow message."""
|
|
45
|
+
|
|
46
|
+
def on_execution(execution: Workflow) -> None:
|
|
47
|
+
# Check if already started or canceled
|
|
48
|
+
if execution.status != WorkflowStatus.NOT_STARTED:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"Execution %s already has status %s, ignoring StartWorkflow",
|
|
51
|
+
execution.id,
|
|
52
|
+
execution.status,
|
|
53
|
+
)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if execution.is_canceled:
|
|
57
|
+
logger.info("Execution %s was canceled before start", execution.id)
|
|
58
|
+
self._terminate(execution)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# Check if start time has expired
|
|
62
|
+
if self._is_after_start_time_expiry(execution):
|
|
63
|
+
logger.warning("Execution %s start time expired, canceling", execution.id)
|
|
64
|
+
self.queue.push(
|
|
65
|
+
CancelWorkflow(
|
|
66
|
+
execution_type=message.execution_type,
|
|
67
|
+
execution_id=message.execution_id,
|
|
68
|
+
user="system",
|
|
69
|
+
reason="Could not begin execution before start time expiry",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# TODO: Check if should queue (concurrent execution limits)
|
|
75
|
+
# if execution.should_queue():
|
|
76
|
+
# self.pending_execution_service.enqueue(execution.pipeline_config_id, message)
|
|
77
|
+
# return
|
|
78
|
+
|
|
79
|
+
self._start(execution, message)
|
|
80
|
+
|
|
81
|
+
self.with_execution(message, on_execution)
|
|
82
|
+
|
|
83
|
+
def _start(
|
|
84
|
+
self,
|
|
85
|
+
execution: Workflow,
|
|
86
|
+
message: StartWorkflow,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Start the execution."""
|
|
89
|
+
initial_stages = execution.initial_stages()
|
|
90
|
+
|
|
91
|
+
if not initial_stages:
|
|
92
|
+
logger.warning("No initial stages found for execution %s", execution.id)
|
|
93
|
+
execution.status = WorkflowStatus.TERMINAL
|
|
94
|
+
self.repository.update_status(execution)
|
|
95
|
+
# Publish ExecutionComplete event
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Mark as running
|
|
99
|
+
execution.status = WorkflowStatus.RUNNING
|
|
100
|
+
execution.start_time = self.current_time_millis()
|
|
101
|
+
self.repository.update_status(execution)
|
|
102
|
+
|
|
103
|
+
# Queue all initial stages
|
|
104
|
+
for stage in initial_stages:
|
|
105
|
+
logger.debug(
|
|
106
|
+
"Queuing initial stage %s (%s) for execution %s",
|
|
107
|
+
stage.name,
|
|
108
|
+
stage.id,
|
|
109
|
+
execution.id,
|
|
110
|
+
)
|
|
111
|
+
self.queue.push(
|
|
112
|
+
StartStage(
|
|
113
|
+
execution_type=message.execution_type,
|
|
114
|
+
execution_id=message.execution_id,
|
|
115
|
+
stage_id=stage.id,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
logger.info(
|
|
120
|
+
"Started execution %s with %d initial stage(s)",
|
|
121
|
+
execution.id,
|
|
122
|
+
len(initial_stages),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _terminate(self, execution: Workflow) -> None:
|
|
126
|
+
"""Terminate a canceled execution."""
|
|
127
|
+
# Publish ExecutionComplete event
|
|
128
|
+
if execution.pipeline_config_id:
|
|
129
|
+
# Queue start waiting executions
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def _is_after_start_time_expiry(self, execution: Workflow) -> bool:
|
|
133
|
+
"""Check if current time is past start time expiry."""
|
|
134
|
+
if execution.start_time_expiry is None:
|
|
135
|
+
return False
|
|
136
|
+
return self.current_time_millis() > execution.start_time_expiry
|
stabilize/launcher.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WorkflowLauncher - creates and starts pipeline executions.
|
|
3
|
+
|
|
4
|
+
This module provides the high-level interface for launching pipelines.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from stabilize.models.stage import StageExecution
|
|
13
|
+
from stabilize.models.workflow import (
|
|
14
|
+
Trigger,
|
|
15
|
+
Workflow,
|
|
16
|
+
)
|
|
17
|
+
from stabilize.stages.builder import StageDefinitionBuilderFactory
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from stabilize.orchestrator import Orchestrator
|
|
21
|
+
from stabilize.persistence.store import WorkflowStore
|
|
22
|
+
from stabilize.tasks.registry import TaskRegistry
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkflowLauncher:
|
|
28
|
+
"""
|
|
29
|
+
Launcher for pipeline executions.
|
|
30
|
+
|
|
31
|
+
Creates executions from configuration and starts them via the runner.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
repository: WorkflowStore,
|
|
37
|
+
runner: Orchestrator,
|
|
38
|
+
stage_builder_factory: StageDefinitionBuilderFactory | None = None,
|
|
39
|
+
task_registry: TaskRegistry | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Initialize the launcher.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
repository: The execution repository
|
|
46
|
+
runner: The execution runner
|
|
47
|
+
stage_builder_factory: Factory for stage builders
|
|
48
|
+
task_registry: Registry for task implementations
|
|
49
|
+
"""
|
|
50
|
+
self.repository = repository
|
|
51
|
+
self.runner = runner
|
|
52
|
+
self.stage_builder_factory = stage_builder_factory or StageDefinitionBuilderFactory()
|
|
53
|
+
self.task_registry = task_registry
|
|
54
|
+
|
|
55
|
+
def start(
|
|
56
|
+
self,
|
|
57
|
+
pipeline_config: dict[str, Any],
|
|
58
|
+
trigger: dict[str, Any] | None = None,
|
|
59
|
+
) -> Workflow:
|
|
60
|
+
"""
|
|
61
|
+
Start a pipeline execution from configuration.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
pipeline_config: Pipeline configuration dictionary
|
|
65
|
+
trigger: Optional trigger information
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The created execution
|
|
69
|
+
"""
|
|
70
|
+
# Parse the execution
|
|
71
|
+
execution = self.parse_execution(pipeline_config, trigger)
|
|
72
|
+
|
|
73
|
+
# Store it
|
|
74
|
+
self.repository.store(execution)
|
|
75
|
+
|
|
76
|
+
# Start it
|
|
77
|
+
self.runner.start(execution)
|
|
78
|
+
|
|
79
|
+
logger.info(f"Launched execution {execution.id} for pipeline {execution.name}")
|
|
80
|
+
|
|
81
|
+
return execution
|
|
82
|
+
|
|
83
|
+
def parse_execution(
|
|
84
|
+
self,
|
|
85
|
+
config: dict[str, Any],
|
|
86
|
+
trigger_config: dict[str, Any] | None = None,
|
|
87
|
+
) -> Workflow:
|
|
88
|
+
"""
|
|
89
|
+
Parse a pipeline configuration into an execution.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
config: Pipeline configuration
|
|
93
|
+
trigger_config: Optional trigger configuration
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A Workflow ready to run
|
|
97
|
+
"""
|
|
98
|
+
# Parse trigger
|
|
99
|
+
trigger = Trigger()
|
|
100
|
+
if trigger_config:
|
|
101
|
+
trigger = Trigger(
|
|
102
|
+
type=trigger_config.get("type", "manual"),
|
|
103
|
+
user=trigger_config.get("user", "anonymous"),
|
|
104
|
+
parameters=trigger_config.get("parameters", {}),
|
|
105
|
+
artifacts=trigger_config.get("artifacts", []),
|
|
106
|
+
payload=trigger_config.get("payload", {}),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Parse stages
|
|
110
|
+
stages = self._parse_stages(config.get("stages", []))
|
|
111
|
+
|
|
112
|
+
# Create execution
|
|
113
|
+
execution = Workflow.create(
|
|
114
|
+
application=config.get("application", "unknown"),
|
|
115
|
+
name=config.get("name", "Unnamed Pipeline"),
|
|
116
|
+
stages=stages,
|
|
117
|
+
trigger=trigger,
|
|
118
|
+
pipeline_config_id=config.get("id"),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Set additional properties
|
|
122
|
+
execution.is_limit_concurrent = config.get("limitConcurrent", False)
|
|
123
|
+
execution.max_concurrent_executions = config.get("maxConcurrentExecutions", 0)
|
|
124
|
+
execution.keep_waiting_pipelines = config.get("keepWaitingPipelines", False)
|
|
125
|
+
|
|
126
|
+
return execution
|
|
127
|
+
|
|
128
|
+
def _parse_stages(
|
|
129
|
+
self,
|
|
130
|
+
stage_configs: list[dict[str, Any]],
|
|
131
|
+
) -> list[StageExecution]:
|
|
132
|
+
"""
|
|
133
|
+
Parse stage configurations into StageExecution objects.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
stage_configs: List of stage configuration dictionaries
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of StageExecution objects
|
|
140
|
+
"""
|
|
141
|
+
stages = []
|
|
142
|
+
|
|
143
|
+
for config in stage_configs:
|
|
144
|
+
stage = self._parse_stage(config)
|
|
145
|
+
stages.append(stage)
|
|
146
|
+
|
|
147
|
+
return stages
|
|
148
|
+
|
|
149
|
+
def _parse_stage(self, config: dict[str, Any]) -> StageExecution:
|
|
150
|
+
"""
|
|
151
|
+
Parse a single stage configuration.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
config: Stage configuration dictionary
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A StageExecution object
|
|
158
|
+
"""
|
|
159
|
+
# Get requisite stages
|
|
160
|
+
requisite_ids: set[str] = set()
|
|
161
|
+
if "requisiteStageRefIds" in config:
|
|
162
|
+
requisite_ids = set(config["requisiteStageRefIds"])
|
|
163
|
+
|
|
164
|
+
# Create context from config (excluding metadata fields)
|
|
165
|
+
context = {
|
|
166
|
+
k: v
|
|
167
|
+
for k, v in config.items()
|
|
168
|
+
if k
|
|
169
|
+
not in {
|
|
170
|
+
"id",
|
|
171
|
+
"refId",
|
|
172
|
+
"type",
|
|
173
|
+
"name",
|
|
174
|
+
"requisiteStageRefIds",
|
|
175
|
+
"parentStageId",
|
|
176
|
+
"syntheticStageOwner",
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stage = StageExecution.create(
|
|
181
|
+
type=config.get("type", "unknown"),
|
|
182
|
+
name=config.get("name", config.get("type", "Unknown")),
|
|
183
|
+
ref_id=config.get("refId", config.get("id", "")),
|
|
184
|
+
context=context,
|
|
185
|
+
requisite_stage_ref_ids=requisite_ids,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Build tasks using stage definition builder
|
|
189
|
+
builder = self.stage_builder_factory.get(stage.type)
|
|
190
|
+
stage.tasks = builder.build_tasks(stage)
|
|
191
|
+
|
|
192
|
+
# Mark first/last tasks
|
|
193
|
+
if stage.tasks:
|
|
194
|
+
stage.tasks[0].stage_start = True
|
|
195
|
+
stage.tasks[-1].stage_end = True
|
|
196
|
+
|
|
197
|
+
return stage
|
|
198
|
+
|
|
199
|
+
def create_orchestration(
|
|
200
|
+
self,
|
|
201
|
+
application: str,
|
|
202
|
+
name: str,
|
|
203
|
+
stages: list[dict[str, Any]],
|
|
204
|
+
) -> Workflow:
|
|
205
|
+
"""
|
|
206
|
+
Create an ad-hoc orchestration (single execution not from a pipeline).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
application: Application name
|
|
210
|
+
name: Orchestration name
|
|
211
|
+
stages: Stage configurations
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
The created execution
|
|
215
|
+
"""
|
|
216
|
+
parsed_stages = self._parse_stages(stages)
|
|
217
|
+
|
|
218
|
+
execution = Workflow.create_orchestration(
|
|
219
|
+
application=application,
|
|
220
|
+
name=name,
|
|
221
|
+
stages=parsed_stages,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.repository.store(execution)
|
|
225
|
+
self.runner.start(execution)
|
|
226
|
+
|
|
227
|
+
return execution
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def create_simple_pipeline(
|
|
231
|
+
name: str,
|
|
232
|
+
application: str,
|
|
233
|
+
stages: list[dict[str, Any]],
|
|
234
|
+
) -> dict[str, Any]:
|
|
235
|
+
"""
|
|
236
|
+
Create a simple pipeline configuration.
|
|
237
|
+
|
|
238
|
+
Helper function for building pipeline configs programmatically.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
name: Pipeline name
|
|
242
|
+
application: Application name
|
|
243
|
+
stages: List of stage configurations
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
A pipeline configuration dictionary
|
|
247
|
+
|
|
248
|
+
Example:
|
|
249
|
+
config = create_simple_pipeline(
|
|
250
|
+
name="Deploy to Prod",
|
|
251
|
+
application="myapp",
|
|
252
|
+
stages=[
|
|
253
|
+
{"refId": "1", "type": "wait", "name": "Wait", "waitTime": 30},
|
|
254
|
+
{"refId": "2", "type": "deploy", "name": "Deploy",
|
|
255
|
+
"requisiteStageRefIds": ["1"]},
|
|
256
|
+
],
|
|
257
|
+
)
|
|
258
|
+
"""
|
|
259
|
+
return {
|
|
260
|
+
"name": name,
|
|
261
|
+
"application": application,
|
|
262
|
+
"stages": stages,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def create_stage_config(
|
|
267
|
+
ref_id: str,
|
|
268
|
+
stage_type: str,
|
|
269
|
+
name: str,
|
|
270
|
+
requisites: list[str] | None = None,
|
|
271
|
+
**kwargs: Any,
|
|
272
|
+
) -> dict[str, Any]:
|
|
273
|
+
"""
|
|
274
|
+
Create a stage configuration.
|
|
275
|
+
|
|
276
|
+
Helper function for building stage configs.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
ref_id: Stage reference ID
|
|
280
|
+
stage_type: Stage type
|
|
281
|
+
name: Stage name
|
|
282
|
+
requisites: Prerequisite stage ref IDs
|
|
283
|
+
**kwargs: Additional stage context
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
A stage configuration dictionary
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
stage = create_stage_config(
|
|
290
|
+
ref_id="1",
|
|
291
|
+
stage_type="wait",
|
|
292
|
+
name="Wait for approval",
|
|
293
|
+
waitTime=3600,
|
|
294
|
+
)
|
|
295
|
+
"""
|
|
296
|
+
config: dict[str, Any] = {
|
|
297
|
+
"refId": ref_id,
|
|
298
|
+
"type": stage_type,
|
|
299
|
+
"name": name,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if requisites:
|
|
303
|
+
config["requisiteStageRefIds"] = requisites
|
|
304
|
+
|
|
305
|
+
config.update(kwargs)
|
|
306
|
+
|
|
307
|
+
return config
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
-- migration: initial_schema
|
|
2
|
+
-- id: 01KDQ4N9QPJ6Q4MCV3V9GHWPV4
|
|
3
|
+
|
|
4
|
+
-- migrate: up
|
|
5
|
+
|
|
6
|
+
CREATE TABLE pipeline_executions (
|
|
7
|
+
id VARCHAR(26) PRIMARY KEY,
|
|
8
|
+
type VARCHAR(50) NOT NULL,
|
|
9
|
+
application VARCHAR(255) NOT NULL,
|
|
10
|
+
name VARCHAR(255),
|
|
11
|
+
status VARCHAR(50) NOT NULL,
|
|
12
|
+
start_time BIGINT,
|
|
13
|
+
end_time BIGINT,
|
|
14
|
+
start_time_expiry BIGINT,
|
|
15
|
+
trigger JSONB,
|
|
16
|
+
context JSONB,
|
|
17
|
+
is_canceled BOOLEAN DEFAULT FALSE,
|
|
18
|
+
canceled_by VARCHAR(255),
|
|
19
|
+
cancellation_reason TEXT,
|
|
20
|
+
paused JSONB,
|
|
21
|
+
pipeline_config_id VARCHAR(255),
|
|
22
|
+
is_limit_concurrent BOOLEAN DEFAULT FALSE,
|
|
23
|
+
max_concurrent_executions INT DEFAULT 0,
|
|
24
|
+
keep_waiting_pipelines BOOLEAN DEFAULT FALSE,
|
|
25
|
+
origin VARCHAR(50) DEFAULT 'unknown',
|
|
26
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE stage_executions (
|
|
30
|
+
id VARCHAR(26) PRIMARY KEY,
|
|
31
|
+
execution_id VARCHAR(26) NOT NULL REFERENCES pipeline_executions(id) ON DELETE CASCADE,
|
|
32
|
+
ref_id VARCHAR(50) NOT NULL,
|
|
33
|
+
type VARCHAR(100) NOT NULL,
|
|
34
|
+
name VARCHAR(255),
|
|
35
|
+
status VARCHAR(50) NOT NULL,
|
|
36
|
+
context JSONB,
|
|
37
|
+
outputs JSONB,
|
|
38
|
+
requisite_stage_ref_ids TEXT[],
|
|
39
|
+
parent_stage_id VARCHAR(26),
|
|
40
|
+
synthetic_stage_owner VARCHAR(20),
|
|
41
|
+
start_time BIGINT,
|
|
42
|
+
end_time BIGINT,
|
|
43
|
+
start_time_expiry BIGINT,
|
|
44
|
+
scheduled_time BIGINT,
|
|
45
|
+
UNIQUE(execution_id, ref_id)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE task_executions (
|
|
49
|
+
id VARCHAR(26) PRIMARY KEY,
|
|
50
|
+
stage_id VARCHAR(26) NOT NULL REFERENCES stage_executions(id) ON DELETE CASCADE,
|
|
51
|
+
name VARCHAR(255) NOT NULL,
|
|
52
|
+
implementing_class VARCHAR(255) NOT NULL,
|
|
53
|
+
status VARCHAR(50) NOT NULL,
|
|
54
|
+
start_time BIGINT,
|
|
55
|
+
end_time BIGINT,
|
|
56
|
+
stage_start BOOLEAN DEFAULT FALSE,
|
|
57
|
+
stage_end BOOLEAN DEFAULT FALSE,
|
|
58
|
+
loop_start BOOLEAN DEFAULT FALSE,
|
|
59
|
+
loop_end BOOLEAN DEFAULT FALSE,
|
|
60
|
+
task_exception_details JSONB
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE TABLE queue_messages (
|
|
64
|
+
id SERIAL PRIMARY KEY,
|
|
65
|
+
message_id VARCHAR(36) NOT NULL UNIQUE,
|
|
66
|
+
message_type VARCHAR(100) NOT NULL,
|
|
67
|
+
payload JSONB NOT NULL,
|
|
68
|
+
deliver_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
69
|
+
attempts INT DEFAULT 0,
|
|
70
|
+
max_attempts INT DEFAULT 10,
|
|
71
|
+
locked_until TIMESTAMP,
|
|
72
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE INDEX idx_executions_application ON pipeline_executions(application);
|
|
76
|
+
CREATE INDEX idx_executions_config_id ON pipeline_executions(pipeline_config_id);
|
|
77
|
+
CREATE INDEX idx_executions_status ON pipeline_executions(status);
|
|
78
|
+
CREATE INDEX idx_stage_execution ON stage_executions(execution_id);
|
|
79
|
+
CREATE INDEX idx_stage_parent ON stage_executions(parent_stage_id);
|
|
80
|
+
CREATE INDEX idx_task_stage ON task_executions(stage_id);
|
|
81
|
+
CREATE INDEX idx_queue_deliver ON queue_messages(deliver_at) WHERE attempts < 10;
|
|
82
|
+
CREATE INDEX idx_queue_locked ON queue_messages(locked_until);
|
|
83
|
+
|
|
84
|
+
-- migrate: down
|
|
85
|
+
|
|
86
|
+
DROP INDEX IF EXISTS idx_queue_locked;
|
|
87
|
+
DROP INDEX IF EXISTS idx_queue_deliver;
|
|
88
|
+
DROP INDEX IF EXISTS idx_task_stage;
|
|
89
|
+
DROP INDEX IF EXISTS idx_stage_parent;
|
|
90
|
+
DROP INDEX IF EXISTS idx_stage_execution;
|
|
91
|
+
DROP INDEX IF EXISTS idx_executions_status;
|
|
92
|
+
DROP INDEX IF EXISTS idx_executions_config_id;
|
|
93
|
+
DROP INDEX IF EXISTS idx_executions_application;
|
|
94
|
+
DROP TABLE IF EXISTS queue_messages;
|
|
95
|
+
DROP TABLE IF EXISTS task_executions;
|
|
96
|
+
DROP TABLE IF EXISTS stage_executions;
|
|
97
|
+
DROP TABLE IF EXISTS pipeline_executions;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- migration: rag_embeddings
|
|
2
|
+
-- id: 01KDRK3TXW4R2GERC1WBCQYJGG
|
|
3
|
+
|
|
4
|
+
-- migrate: up
|
|
5
|
+
|
|
6
|
+
CREATE TABLE rag_embeddings (
|
|
7
|
+
id SERIAL PRIMARY KEY,
|
|
8
|
+
doc_id VARCHAR(255) NOT NULL,
|
|
9
|
+
content TEXT NOT NULL,
|
|
10
|
+
embedding JSONB NOT NULL,
|
|
11
|
+
embedding_model VARCHAR(100) NOT NULL,
|
|
12
|
+
chunk_index INT DEFAULT 0,
|
|
13
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
14
|
+
updated_at TIMESTAMP DEFAULT NOW(),
|
|
15
|
+
UNIQUE(doc_id, chunk_index, embedding_model)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX idx_rag_embeddings_doc ON rag_embeddings(doc_id);
|
|
19
|
+
CREATE INDEX idx_rag_embeddings_model ON rag_embeddings(embedding_model);
|
|
20
|
+
|
|
21
|
+
-- migrate: down
|
|
22
|
+
|
|
23
|
+
DROP INDEX IF EXISTS idx_rag_embeddings_model;
|
|
24
|
+
DROP INDEX IF EXISTS idx_rag_embeddings_doc;
|
|
25
|
+
DROP TABLE IF EXISTS rag_embeddings;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Stabilize database migrations for PostgreSQL."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Core data models for pipeline execution."""
|
|
2
|
+
|
|
3
|
+
from stabilize.models.stage import StageExecution, SyntheticStageOwner
|
|
4
|
+
from stabilize.models.status import WorkflowStatus
|
|
5
|
+
from stabilize.models.task import TaskExecution
|
|
6
|
+
from stabilize.models.workflow import Workflow, WorkflowType
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"WorkflowStatus",
|
|
10
|
+
"TaskExecution",
|
|
11
|
+
"StageExecution",
|
|
12
|
+
"SyntheticStageOwner",
|
|
13
|
+
"Workflow",
|
|
14
|
+
"WorkflowType",
|
|
15
|
+
]
|