flowsense-engine 0.3.0__tar.gz → 0.4.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 (116) hide show
  1. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/CHANGELOG.md +27 -0
  2. {flowsense_engine-0.3.0/src/flowsense_engine.egg-info → flowsense_engine-0.4.0}/PKG-INFO +52 -8
  3. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/README.md +51 -7
  4. flowsense_engine-0.4.0/docs/alerting.md +57 -0
  5. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/docs/architecture.md +19 -3
  6. flowsense_engine-0.4.0/docs/authentication.md +77 -0
  7. flowsense_engine-0.4.0/docs/grafana.md +66 -0
  8. flowsense_engine-0.4.0/docs/prometheus.md +58 -0
  9. flowsense_engine-0.4.0/docs/python-api.md +142 -0
  10. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/docs/releasing.md +4 -4
  11. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/pyproject.toml +1 -1
  12. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/__init__.py +16 -0
  13. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/__init__.py +24 -2
  14. flowsense_engine-0.4.0/src/flowsense/application/batch.py +129 -0
  15. flowsense_engine-0.4.0/src/flowsense/application/client.py +61 -0
  16. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/output.py +17 -0
  17. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/ports.py +20 -0
  18. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/serialization.py +35 -0
  19. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/cli/main.py +35 -0
  20. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/__init__.py +17 -1
  21. flowsense_engine-0.4.0/src/flowsense/infrastructure/airflow/auth.py +63 -0
  22. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/client.py +54 -23
  23. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/config.py +24 -8
  24. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/dto.py +5 -0
  25. flowsense_engine-0.4.0/src/flowsense/infrastructure/airflow/factory.py +35 -0
  26. flowsense_engine-0.4.0/src/flowsense/observability/__init__.py +5 -0
  27. flowsense_engine-0.4.0/src/flowsense/observability/prometheus.py +248 -0
  28. flowsense_engine-0.4.0/src/flowsense/observability/service.py +90 -0
  29. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0/src/flowsense_engine.egg-info}/PKG-INFO +52 -8
  30. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense_engine.egg-info/SOURCES.txt +16 -0
  31. flowsense_engine-0.4.0/tests/test_airflow_auth.py +114 -0
  32. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_compatibility.py +61 -0
  33. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_factory.py +32 -2
  34. flowsense_engine-0.4.0/tests/test_batch_analysis_output.py +71 -0
  35. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_cli.py +33 -1
  36. flowsense_engine-0.4.0/tests/test_library_client.py +169 -0
  37. flowsense_engine-0.4.0/tests/test_observability_assets.py +58 -0
  38. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_package_metadata.py +1 -1
  39. flowsense_engine-0.4.0/tests/test_prometheus.py +152 -0
  40. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_public_api.py +8 -0
  41. flowsense_engine-0.3.0/src/flowsense/infrastructure/airflow/factory.py +0 -12
  42. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/LICENSE +0 -0
  43. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/MANIFEST.in +0 -0
  44. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/docs/benchmarks.md +0 -0
  45. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/docs/migrating-to-0.3.md +0 -0
  46. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/setup.cfg +0 -0
  47. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/analysis_engine.py +0 -0
  48. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/analyzer.py +0 -0
  49. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/pipeline.py +0 -0
  50. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/request.py +0 -0
  51. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/application/use_cases.py +0 -0
  52. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/cli/__init__.py +0 -0
  53. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/cli/report.py +0 -0
  54. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/collector/__init__.py +0 -0
  55. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/collector/airflow_client.py +0 -0
  56. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/config.py +0 -0
  57. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/__init__.py +0 -0
  58. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/enums.py +0 -0
  59. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/exceptions.py +0 -0
  60. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/models.py +0 -0
  61. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/policy.py +0 -0
  62. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/domain/results.py +0 -0
  63. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/__init__.py +0 -0
  64. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/analyzer.py +0 -0
  65. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/change_point.py +0 -0
  66. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/drift.py +0 -0
  67. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/history.py +0 -0
  68. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/impact.py +0 -0
  69. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/propagation.py +0 -0
  70. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/root_cause.py +0 -0
  71. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/timing.py +0 -0
  72. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/trend.py +0 -0
  73. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/engine/validation.py +0 -0
  74. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/__init__.py +0 -0
  75. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/exceptions.py +0 -0
  76. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/infrastructure/airflow/mapper.py +0 -0
  77. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/mcp/__init__.py +0 -0
  78. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/mcp/server.py +0 -0
  79. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/models/__init__.py +0 -0
  80. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/models/dag_analysis.py +0 -0
  81. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/models/task_run.py +0 -0
  82. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/py.typed +0 -0
  83. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense/version.py +0 -0
  84. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense_engine.egg-info/dependency_links.txt +0 -0
  85. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense_engine.egg-info/entry_points.txt +0 -0
  86. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense_engine.egg-info/requires.txt +0 -0
  87. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/src/flowsense_engine.egg-info/top_level.txt +0 -0
  88. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_client.py +0 -0
  89. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_client_integration.py +0 -0
  90. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_contract.py +0 -0
  91. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_mapper.py +0 -0
  92. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_airflow_resilience.py +0 -0
  93. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analysis_golden.py +0 -0
  94. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analysis_output.py +0 -0
  95. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analysis_pipeline.py +0 -0
  96. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analysis_request.py +0 -0
  97. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analysis_use_case.py +0 -0
  98. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_analyzer.py +0 -0
  99. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_architecture.py +0 -0
  100. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_benchmark_analysis.py +0 -0
  101. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_change_point.py +0 -0
  102. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_cli_report.py +0 -0
  103. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_dag_summary.py +0 -0
  104. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_deprecations.py +0 -0
  105. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_domain.py +0 -0
  106. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_drift.py +0 -0
  107. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_history.py +0 -0
  108. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_impact.py +0 -0
  109. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_mcp_integration.py +0 -0
  110. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_mcp_server.py +0 -0
  111. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_observation_validation.py +0 -0
  112. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_policy.py +0 -0
  113. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_propagation.py +0 -0
  114. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_root_cause.py +0 -0
  115. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_timing.py +0 -0
  116. {flowsense_engine-0.3.0 → flowsense_engine-0.4.0}/tests/test_trend.py +0 -0
@@ -5,6 +5,33 @@ All notable changes to FlowSense are documented in this file. The project uses
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.4.0] - 2026-09-20
9
+
10
+ ### Added
11
+
12
+ - Added paginated Airflow 2 and 3 DAG discovery with optional paused-DAG
13
+ inclusion.
14
+ - Added an immutable multi-DAG analysis result and bounded, opt-in concurrency
15
+ through `FlowSenseClient.analyze_many()`.
16
+ - Added a versioned, typed batch output document with serialization and JSON
17
+ Schema helpers.
18
+ - Added the `FlowSenseClient` facade for concise embedded Python usage and an
19
+ explicit Airflow data-source factory for application-owned configuration.
20
+ - Added pluggable Airflow authentication providers for Basic Auth, login-token
21
+ exchange, static or rotating bearer tokens, and deployment-specific headers.
22
+ - Added a bounded Prometheus metrics exporter and the optional
23
+ `flowsense serve-metrics` command for continuous DAG analysis.
24
+ - Added a provisioned Grafana dashboard and local Docker Compose example for
25
+ inspecting analysis health, coverage, severity, task drift, and handoffs.
26
+ - Added Prometheus alert rules, Alertmanager configuration, synthetic rule
27
+ tests, and CI validation for FlowSense analysis failures and anomalies.
28
+
29
+ ### Changed
30
+
31
+ - Kept observability integrations as optional delivery adapters so the core
32
+ analysis package remains independent from Prometheus, Grafana, and hosted
33
+ service infrastructure.
34
+
8
35
  ## [0.3.0] - 2026-09-20
9
36
 
10
37
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowsense-engine
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Temporal drift and anomaly detection for Apache Airflow
5
5
  Author: Omer Cengiz
6
6
  License-Expression: Apache-2.0
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
 
38
38
  Temporal drift, anomaly detection, and dependency-aware propagation analysis for Apache Airflow.
39
39
 
40
- Current package release: `0.3.0`. See [CHANGELOG.md](CHANGELOG.md) for release
40
+ Current package release: `0.4.0`. See [CHANGELOG.md](CHANGELOG.md) for release
41
41
  notes, [docs/architecture.md](docs/architecture.md) for the system architecture,
42
42
  [docs/benchmarks.md](docs/benchmarks.md) for performance measurement, and
43
43
  [docs/migrating-to-0.3.md](docs/migrating-to-0.3.md) for migration guidance.
@@ -180,6 +180,7 @@ AIRFLOW_USERNAME=your_username
180
180
  AIRFLOW_PASSWORD=your_password
181
181
  AIRFLOW_API_VERSION=v2
182
182
  AIRFLOW_AUTH_MODE=token
183
+ # AIRFLOW_BEARER_TOKEN=your_static_token
183
184
  AIRFLOW_CONNECT_TIMEOUT=10
184
185
  AIRFLOW_READ_TIMEOUT=10
185
186
  AIRFLOW_MAX_RETRIES=2
@@ -192,6 +193,12 @@ Stable REST API deployments. Airflow 3.x uses the `v2` API and typically uses
192
193
  token authentication. Authentication still depends on the API auth backend
193
194
  configured in the Airflow deployment.
194
195
 
196
+ Static bearer tokens can use `AIRFLOW_AUTH_MODE=bearer` with
197
+ `AIRFLOW_BEARER_TOKEN`; username and password are not required in that mode.
198
+ Library integrations may inject a custom `AirflowAuthProvider` for rotating
199
+ tokens, identity-aware proxies, or deployment-specific headers. See
200
+ [docs/authentication.md](docs/authentication.md) for the complete contract.
201
+
195
202
  CI verifies both integrations through versioned Airflow 2 and Airflow 3 REST
196
203
  contract fixtures. These boundary tests cover authentication, endpoint routing,
197
204
  response validation, domain mapping, and the complete analysis use case.
@@ -279,21 +286,41 @@ tooling, or client code generation.
279
286
 
280
287
  ## Library API
281
288
 
282
- FlowSense can also be used as a Python library through its supported top-level
283
- API:
289
+ `FlowSenseClient` is the recommended in-process Python API. The environment
290
+ configured Airflow factory keeps the basic setup concise:
284
291
 
285
292
  ```python
286
- from flowsense import AnalysisRequest, AnalyzeDAG
293
+ from flowsense import FlowSenseClient
287
294
  from flowsense.infrastructure.airflow import create_airflow_data_source
288
295
 
289
- analysis = AnalyzeDAG(create_airflow_data_source).execute(
290
- AnalysisRequest(dag_id="flowsense_demo")
291
- )
296
+ client = FlowSenseClient(create_airflow_data_source)
297
+ analysis = client.analyze("flowsense_demo")
292
298
 
293
299
  print(analysis.overall_severity)
294
300
  print(analysis.primary_origin)
295
301
  ```
296
302
 
303
+ Applications that own configuration can combine `AirflowConfig` with
304
+ `create_airflow_data_source_factory`. Typed integrations may continue to call
305
+ `client.execute(AnalysisRequest(...))`. See the complete
306
+ [Python API guide](docs/python-api.md).
307
+
308
+ Multiple DAGs can be analyzed with bounded, opt-in concurrency. Expected
309
+ FlowSense failures are isolated per DAG:
310
+
311
+ ```python
312
+ result = client.analyze_many(
313
+ ["orders", "payments", "inventory"],
314
+ max_concurrency=3,
315
+ )
316
+ ```
317
+
318
+ `AirflowClient.list_dag_ids()` provides paginated DAG discovery and excludes
319
+ paused DAGs by default.
320
+
321
+ `serialize_batch_analysis(result)` converts the result into the stable batch
322
+ output contract without exposing raw exception objects.
323
+
297
324
  Analysis behavior can be customized with an immutable policy:
298
325
 
299
326
  ```python
@@ -351,6 +378,23 @@ The server exposes the `analyze_airflow_dag` tool, which returns task drift,
351
378
  handoff drift, impact classification, propagation paths, and primary root-cause
352
379
  information for a DAG.
353
380
 
381
+ ## Prometheus
382
+
383
+ Run continuous analysis and expose the latest DAG and bounded task-level metrics:
384
+
385
+ ```bash
386
+ flowsense serve-metrics example_dag --host 0.0.0.0 --port 9108
387
+ ```
388
+
389
+ See [docs/prometheus.md](docs/prometheus.md) for the metric contract,
390
+ cardinality policy, and Prometheus scrape configuration.
391
+
392
+ A provisioned Prometheus and Grafana development stack, including the
393
+ `FlowSense Overview` dashboard, is documented in
394
+ [docs/grafana.md](docs/grafana.md).
395
+ Default operational alerts and Alertmanager routing are documented in
396
+ [docs/alerting.md](docs/alerting.md).
397
+
354
398
  ## Development
355
399
 
356
400
  Run unit tests:
@@ -2,7 +2,7 @@
2
2
 
3
3
  Temporal drift, anomaly detection, and dependency-aware propagation analysis for Apache Airflow.
4
4
 
5
- Current package release: `0.3.0`. See [CHANGELOG.md](CHANGELOG.md) for release
5
+ Current package release: `0.4.0`. See [CHANGELOG.md](CHANGELOG.md) for release
6
6
  notes, [docs/architecture.md](docs/architecture.md) for the system architecture,
7
7
  [docs/benchmarks.md](docs/benchmarks.md) for performance measurement, and
8
8
  [docs/migrating-to-0.3.md](docs/migrating-to-0.3.md) for migration guidance.
@@ -145,6 +145,7 @@ AIRFLOW_USERNAME=your_username
145
145
  AIRFLOW_PASSWORD=your_password
146
146
  AIRFLOW_API_VERSION=v2
147
147
  AIRFLOW_AUTH_MODE=token
148
+ # AIRFLOW_BEARER_TOKEN=your_static_token
148
149
  AIRFLOW_CONNECT_TIMEOUT=10
149
150
  AIRFLOW_READ_TIMEOUT=10
150
151
  AIRFLOW_MAX_RETRIES=2
@@ -157,6 +158,12 @@ Stable REST API deployments. Airflow 3.x uses the `v2` API and typically uses
157
158
  token authentication. Authentication still depends on the API auth backend
158
159
  configured in the Airflow deployment.
159
160
 
161
+ Static bearer tokens can use `AIRFLOW_AUTH_MODE=bearer` with
162
+ `AIRFLOW_BEARER_TOKEN`; username and password are not required in that mode.
163
+ Library integrations may inject a custom `AirflowAuthProvider` for rotating
164
+ tokens, identity-aware proxies, or deployment-specific headers. See
165
+ [docs/authentication.md](docs/authentication.md) for the complete contract.
166
+
160
167
  CI verifies both integrations through versioned Airflow 2 and Airflow 3 REST
161
168
  contract fixtures. These boundary tests cover authentication, endpoint routing,
162
169
  response validation, domain mapping, and the complete analysis use case.
@@ -244,21 +251,41 @@ tooling, or client code generation.
244
251
 
245
252
  ## Library API
246
253
 
247
- FlowSense can also be used as a Python library through its supported top-level
248
- API:
254
+ `FlowSenseClient` is the recommended in-process Python API. The environment
255
+ configured Airflow factory keeps the basic setup concise:
249
256
 
250
257
  ```python
251
- from flowsense import AnalysisRequest, AnalyzeDAG
258
+ from flowsense import FlowSenseClient
252
259
  from flowsense.infrastructure.airflow import create_airflow_data_source
253
260
 
254
- analysis = AnalyzeDAG(create_airflow_data_source).execute(
255
- AnalysisRequest(dag_id="flowsense_demo")
256
- )
261
+ client = FlowSenseClient(create_airflow_data_source)
262
+ analysis = client.analyze("flowsense_demo")
257
263
 
258
264
  print(analysis.overall_severity)
259
265
  print(analysis.primary_origin)
260
266
  ```
261
267
 
268
+ Applications that own configuration can combine `AirflowConfig` with
269
+ `create_airflow_data_source_factory`. Typed integrations may continue to call
270
+ `client.execute(AnalysisRequest(...))`. See the complete
271
+ [Python API guide](docs/python-api.md).
272
+
273
+ Multiple DAGs can be analyzed with bounded, opt-in concurrency. Expected
274
+ FlowSense failures are isolated per DAG:
275
+
276
+ ```python
277
+ result = client.analyze_many(
278
+ ["orders", "payments", "inventory"],
279
+ max_concurrency=3,
280
+ )
281
+ ```
282
+
283
+ `AirflowClient.list_dag_ids()` provides paginated DAG discovery and excludes
284
+ paused DAGs by default.
285
+
286
+ `serialize_batch_analysis(result)` converts the result into the stable batch
287
+ output contract without exposing raw exception objects.
288
+
262
289
  Analysis behavior can be customized with an immutable policy:
263
290
 
264
291
  ```python
@@ -316,6 +343,23 @@ The server exposes the `analyze_airflow_dag` tool, which returns task drift,
316
343
  handoff drift, impact classification, propagation paths, and primary root-cause
317
344
  information for a DAG.
318
345
 
346
+ ## Prometheus
347
+
348
+ Run continuous analysis and expose the latest DAG and bounded task-level metrics:
349
+
350
+ ```bash
351
+ flowsense serve-metrics example_dag --host 0.0.0.0 --port 9108
352
+ ```
353
+
354
+ See [docs/prometheus.md](docs/prometheus.md) for the metric contract,
355
+ cardinality policy, and Prometheus scrape configuration.
356
+
357
+ A provisioned Prometheus and Grafana development stack, including the
358
+ `FlowSense Overview` dashboard, is documented in
359
+ [docs/grafana.md](docs/grafana.md).
360
+ Default operational alerts and Alertmanager routing are documented in
361
+ [docs/alerting.md](docs/alerting.md).
362
+
319
363
  ## Development
320
364
 
321
365
  Run unit tests:
@@ -0,0 +1,57 @@
1
+ # Prometheus alerting
2
+
3
+ The local observability stack loads a tested set of FlowSense alert rules and
4
+ routes firing alerts to Alertmanager.
5
+
6
+ ## Included alerts
7
+
8
+ | Alert | Default condition | Severity |
9
+ | --- | --- | --- |
10
+ | `FlowSenseExporterDown` | Metrics endpoint unavailable for 2 minutes | Critical |
11
+ | `FlowSenseAnalysisFailed` | Latest analysis failed for 2 minutes | Warning |
12
+ | `FlowSenseAnalysisStale` | No successful analysis for 10 minutes in total | Warning |
13
+ | `FlowSenseCriticalDAG` | DAG severity remains critical for 2 minutes | Critical |
14
+ | `FlowSenseCriticalTask` | Task severity remains critical for 2 minutes | Critical |
15
+ | `FlowSenseTaskMetricsDropped` | Cardinality limit omits tasks for 5 minutes | Warning |
16
+ | `FlowSenseAnalysisSlow` | Analysis remains above 30 seconds for 5 minutes | Warning |
17
+
18
+ The stale rule expression becomes true after five minutes without a successful
19
+ analysis and must remain true for another five minutes before firing. Adjust
20
+ thresholds in `deploy/observability/alerts.yml` to match the configured analysis
21
+ interval and operational expectations.
22
+
23
+ ## Validate rules
24
+
25
+ Run syntax and unit checks with the Prometheus image pinned by the Compose file:
26
+
27
+ ```bash
28
+ docker run --rm \
29
+ --entrypoint promtool \
30
+ -v "$PWD/deploy/observability:/workspace:ro" \
31
+ -w /workspace \
32
+ prom/prometheus:v3.14.0 \
33
+ check rules alerts.yml
34
+
35
+ docker run --rm \
36
+ --entrypoint promtool \
37
+ -v "$PWD/deploy/observability:/workspace:ro" \
38
+ -w /workspace \
39
+ prom/prometheus:v3.14.0 \
40
+ test rules alerts.test.yml
41
+ ```
42
+
43
+ ## Configure notifications
44
+
45
+ The committed Alertmanager configuration intentionally uses an empty local
46
+ receiver. Firing alerts are visible in the Alertmanager UI and the Grafana
47
+ dashboard, but no external notification is sent by default.
48
+
49
+ For production, replace the receiver in
50
+ `deploy/observability/alertmanager.yml` with the organization's managed email,
51
+ Slack, PagerDuty, webhook, or other supported integration. Keep credentials out
52
+ of Git and inject them through the deployment platform's secret management
53
+ mechanism.
54
+
55
+ Alertmanager is available locally at `http://localhost:9093`. Verify routing and
56
+ notification delivery in a non-production environment before enabling it for
57
+ operational incidents.
@@ -48,6 +48,17 @@ implemented by infrastructure adapters. The existing functional
48
48
  `analyze_dag(dag_id, source, policy)` API remains available for callers that
49
49
  already own a data source.
50
50
 
51
+ `FlowSenseClient` is the supported convenience facade for embedded Python
52
+ consumers. It translates the concise `analyze(...)` call into an
53
+ `AnalysisRequest` and delegates to `AnalyzeDAG`; it contains no collection or
54
+ analysis logic of its own. Its optional multi-DAG operation creates one
55
+ use-case execution and context-managed data source per DAG, preserving the
56
+ single-DAG application boundary.
57
+
58
+ `BatchAnalysisDocument` is a separate versioned envelope around the existing
59
+ single-DAG `AnalysisDocument`. Expected failures cross the output boundary as
60
+ typed failure records rather than runtime exception objects.
61
+
51
62
  `DAGAnalysisEngine` is the coarse-grained algorithm extension port. The default
52
63
  implementation owns the complete statistical workflow after data collection.
53
64
  Alternative engines can be injected into `AnalyzeDAG` without changing CLI,
@@ -66,12 +77,17 @@ are required.
66
77
  variables. `load_airflow_config()` and `create_airflow_data_source()` belong to
67
78
  the infrastructure composition boundary.
68
79
 
80
+ Authentication is supplied through `AirflowAuthProvider`. Built-in providers
81
+ cover Basic Auth, Airflow login-token exchange, static bearer tokens, and custom
82
+ headers without coupling request collection to a deployment's identity system.
83
+
69
84
  ### Delivery adapters
70
85
 
71
- `flowsense.cli` and `flowsense.mcp` translate user input into an
86
+ `flowsense.cli`, `flowsense.mcp`, and `flowsense.observability` translate user input into an
72
87
  `AnalysisRequest`, invoke `AnalyzeDAG`, and translate the result into their own
73
- output mechanism. Business analysis and data-collection orchestration must not
74
- be duplicated in these adapters.
88
+ output mechanism. Prometheus export implements the `AnalysisMetricsSink` port
89
+ and keeps a bounded latest-value snapshot. Business analysis and data-collection
90
+ orchestration must not be duplicated in these adapters.
75
91
 
76
92
  ## Main analysis flow
77
93
 
@@ -0,0 +1,77 @@
1
+ # Airflow authentication
2
+
3
+ FlowSense keeps Airflow authentication behind the `AirflowAuthProvider` port.
4
+ The collector does not need to know whether credentials come from Airflow,
5
+ a reverse proxy, a secret manager, or an external identity system.
6
+
7
+ ## Built-in modes
8
+
9
+ ### Airflow login token
10
+
11
+ This remains the default for Airflow 3 deployments that expose `/auth/token`:
12
+
13
+ ```env
14
+ AIRFLOW_AUTH_MODE=token
15
+ AIRFLOW_USERNAME=airflow
16
+ AIRFLOW_PASSWORD=secret
17
+ ```
18
+
19
+ FlowSense exchanges the credentials once and caches the returned bearer token
20
+ for the lifetime of the client.
21
+
22
+ ### Basic authentication
23
+
24
+ Common Airflow 2 Stable REST API deployments can use:
25
+
26
+ ```env
27
+ AIRFLOW_API_VERSION=v1
28
+ AIRFLOW_AUTH_MODE=basic
29
+ AIRFLOW_USERNAME=airflow
30
+ AIRFLOW_PASSWORD=secret
31
+ ```
32
+
33
+ ### Static bearer token
34
+
35
+ For deployments that issue a token outside Airflow:
36
+
37
+ ```env
38
+ AIRFLOW_AUTH_MODE=bearer
39
+ AIRFLOW_BEARER_TOKEN=secret-token
40
+ ```
41
+
42
+ Username and password are not required in bearer mode. Secrets are excluded
43
+ from the `AirflowConfig` representation, but applications must still keep them
44
+ out of logs, shell history, source control, and command-line arguments.
45
+
46
+ ## Custom provider
47
+
48
+ Enterprise integrations can inject an `AirflowAuthProvider` directly. This is
49
+ appropriate for rotating tokens, identity-aware proxies, workload identity, or
50
+ deployment-specific headers:
51
+
52
+ ```python
53
+ from flowsense.infrastructure.airflow import (
54
+ AirflowClient,
55
+ AirflowConfig,
56
+ HeaderAuthProvider,
57
+ )
58
+
59
+ provider = HeaderAuthProvider({"X-Forwarded-User": "flowsense"})
60
+ client = AirflowClient(
61
+ AirflowConfig(base_url="https://airflow.example.com"),
62
+ auth_provider=provider,
63
+ )
64
+ ```
65
+
66
+ For rotating bearer credentials, pass a callable. It is evaluated for every
67
+ Airflow request:
68
+
69
+ ```python
70
+ from flowsense.infrastructure.airflow import BearerTokenAuthProvider
71
+
72
+ provider = BearerTokenAuthProvider(load_current_token)
73
+ ```
74
+
75
+ Custom providers should return only authentication material and leave request
76
+ execution, retry, timeout, payload validation, and error translation to
77
+ `AirflowClient`.
@@ -0,0 +1,66 @@
1
+ # Grafana dashboard
2
+
3
+ FlowSense ships a provisioned local observability stack containing Prometheus
4
+ and Grafana. Airflow credentials stay in the host process running FlowSense;
5
+ the containers only scrape the exported metrics.
6
+
7
+ ## Start FlowSense metrics
8
+
9
+ Configure the required `AIRFLOW_*` environment variables, then run:
10
+
11
+ ```bash
12
+ flowsense serve-metrics example_dag another_dag \
13
+ --host 0.0.0.0 \
14
+ --port 9108 \
15
+ --interval-seconds 60
16
+ ```
17
+
18
+ Binding to `0.0.0.0` makes the endpoint reachable from the local Prometheus
19
+ container. Do not expose this port to untrusted networks without an appropriate
20
+ network policy or reverse proxy.
21
+
22
+ ## Start Prometheus and Grafana
23
+
24
+ From the repository root:
25
+
26
+ ```bash
27
+ docker compose -f deploy/observability/docker-compose.yml up -d
28
+ ```
29
+
30
+ Open:
31
+
32
+ - Grafana: `http://localhost:3000` (`admin` / `admin` by default)
33
+ - Prometheus: `http://localhost:9090`
34
+ - Alertmanager: `http://localhost:9093`
35
+
36
+ Set `GRAFANA_ADMIN_USER` and `GRAFANA_ADMIN_PASSWORD` before starting Compose
37
+ to override the local defaults. The Prometheus data source and `FlowSense
38
+ Overview` dashboard are provisioned automatically.
39
+
40
+ ## Dashboard panels
41
+
42
+ The dashboard provides:
43
+
44
+ - current DAG severity and analysis health;
45
+ - analysis coverage and runtime;
46
+ - anomalous task and handoff counts;
47
+ - DAG severity history;
48
+ - task duration deviation and robust z-score history;
49
+ - current task severity;
50
+ - task cardinality omissions;
51
+ - last successful analysis time.
52
+ - currently firing FlowSense alerts.
53
+
54
+ Use the DAG and task variables at the top of the dashboard to narrow the view.
55
+ The default time range is six hours and the dashboard refreshes every 30 seconds.
56
+ See [alerting.md](alerting.md) for rule behavior, validation, and production
57
+ notification configuration.
58
+
59
+ ## Stop the stack
60
+
61
+ ```bash
62
+ docker compose -f deploy/observability/docker-compose.yml down
63
+ ```
64
+
65
+ Prometheus and Grafana data remain in named volumes. To explicitly remove those
66
+ local volumes, add `--volumes` to the command.
@@ -0,0 +1,58 @@
1
+ # Prometheus metrics
2
+
3
+ FlowSense can continuously analyze one or more Airflow DAGs and expose the
4
+ latest results using the Prometheus text exposition format. The endpoint uses
5
+ only the Python standard library, so no additional runtime dependency is
6
+ required.
7
+
8
+ Start the service:
9
+
10
+ ```bash
11
+ flowsense serve-metrics example_dag another_dag \
12
+ --host 0.0.0.0 \
13
+ --port 9108 \
14
+ --interval-seconds 60
15
+ ```
16
+
17
+ The command uses the same `AIRFLOW_*` configuration as `flowsense analyze`.
18
+ Configure Prometheus to scrape `http://<flowsense-host>:9108/metrics`.
19
+
20
+ ## Metric contract
21
+
22
+ All metrics are gauges. DAG-level metrics carry only the `dag_id` label:
23
+
24
+ - `flowsense_analysis_success`
25
+ - `flowsense_analysis_duration_seconds`
26
+ - `flowsense_analysis_last_success_timestamp_seconds`
27
+ - `flowsense_analysis_last_failure_timestamp_seconds`
28
+ - `flowsense_dag_overall_severity`
29
+ - `flowsense_dag_runs_analyzed`
30
+ - `flowsense_dag_analysis_coverage_ratio`
31
+ - `flowsense_dag_anomalous_tasks`
32
+ - `flowsense_dag_anomalous_handoffs`
33
+ - `flowsense_dag_affected_tasks`
34
+ - `flowsense_dag_change_points`
35
+ - `flowsense_dag_trends`
36
+ - `flowsense_dag_diagnostics`
37
+ - `flowsense_dag_dropped_tasks`
38
+
39
+ Task-level metrics additionally carry `task_id`:
40
+
41
+ - `flowsense_task_severity`
42
+ - `flowsense_task_deviation_percent`
43
+ - `flowsense_task_robust_z_score`
44
+
45
+ Severity values are `0` for normal, `1` for medium, `2` for high, and `3` for
46
+ critical.
47
+
48
+ ## Cardinality policy
49
+
50
+ Task metrics are limited to 200 task IDs per DAG by default. Task IDs are sorted
51
+ to make selection deterministic. Use `--max-tasks-per-dag` to change the
52
+ limit, or set it to `0` to publish DAG-level metrics only. The exporter reports
53
+ the number of omitted tasks through `flowsense_dag_dropped_tasks`.
54
+
55
+ The exporter retains the latest successful analysis snapshot when a later
56
+ collection fails. In that case `flowsense_analysis_success` becomes `0` and the
57
+ failure timestamp advances, while the last known DAG and task measurements
58
+ remain available.
@@ -0,0 +1,142 @@
1
+ # Python library API
2
+
3
+ `FlowSenseClient` is the recommended entry point for applications embedding
4
+ FlowSense. It is a lightweight in-process facade: it does not start a server,
5
+ own a scheduler, or store analysis results.
6
+
7
+ ## Environment-configured Airflow
8
+
9
+ Use the standard Airflow environment variables for the shortest setup:
10
+
11
+ ```python
12
+ from flowsense import FlowSenseClient
13
+ from flowsense.infrastructure.airflow import create_airflow_data_source
14
+
15
+ client = FlowSenseClient(create_airflow_data_source)
16
+ analysis = client.analyze("example_dag")
17
+
18
+ print(analysis.overall_severity)
19
+ print(analysis.primary_origin)
20
+ ```
21
+
22
+ ## Explicit Airflow configuration
23
+
24
+ Applications that own their configuration should avoid process-global
25
+ environment state:
26
+
27
+ ```python
28
+ from flowsense import FlowSenseClient
29
+ from flowsense.infrastructure.airflow import (
30
+ AirflowConfig,
31
+ BasicAuthProvider,
32
+ create_airflow_data_source_factory,
33
+ )
34
+
35
+ config = AirflowConfig(
36
+ base_url="https://airflow.example.com",
37
+ api_version="v2",
38
+ history_run_limit=100,
39
+ )
40
+ source_factory = create_airflow_data_source_factory(
41
+ config,
42
+ auth_provider=BasicAuthProvider("flowsense", "secret"),
43
+ )
44
+ client = FlowSenseClient(source_factory)
45
+
46
+ analysis = client.analyze(
47
+ "example_dag",
48
+ history_run_limit=50,
49
+ dag_run_id="scheduled__2026-09-20T00:00:00+00:00",
50
+ )
51
+ ```
52
+
53
+ Secrets are deliberately supplied to an authentication provider rather than
54
+ stored in application code. See [authentication.md](authentication.md) for
55
+ built-in and custom providers.
56
+
57
+ ## Typed requests and policies
58
+
59
+ Use `execute()` when a request is created at another application boundary:
60
+
61
+ ```python
62
+ from flowsense import AnalysisPolicy, AnalysisRequest
63
+
64
+ request = AnalysisRequest(
65
+ dag_id="example_dag",
66
+ history_run_limit=50,
67
+ policy=AnalysisPolicy(
68
+ minimum_history=10,
69
+ baseline_window=30,
70
+ ),
71
+ )
72
+ analysis = client.execute(request)
73
+ ```
74
+
75
+ Both `analyze()` and `execute()` return the domain-level `DAGAnalysis` model.
76
+ Use `serialize_analysis()` or `build_analysis_document()` when a versioned
77
+ external output contract is required.
78
+
79
+ ## Analyze multiple DAGs
80
+
81
+ Airflow-backed applications can discover active DAG ids before starting a
82
+ batch. Discovery uses the configured, paginated Airflow 2 or 3 REST API:
83
+
84
+ ```python
85
+ from flowsense.infrastructure.airflow import AirflowClient
86
+
87
+ with AirflowClient(config, auth_provider=auth_provider) as airflow:
88
+ dag_ids = airflow.list_dag_ids()
89
+
90
+ result = client.analyze_many(dag_ids, max_concurrency=3)
91
+ ```
92
+
93
+ Paused DAGs are excluded by default. Pass `include_paused=True` when they are
94
+ also required.
95
+
96
+ `analyze_many()` is intended for applications that monitor several DAGs in one
97
+ process. It preserves the requested DAG order and separates successful analyses
98
+ from expected FlowSense failures:
99
+
100
+ ```python
101
+ result = client.analyze_many(
102
+ ["orders", "payments", "inventory"],
103
+ history_run_limit=50,
104
+ max_concurrency=3,
105
+ )
106
+
107
+ for dag_id, analysis in result.analyses.items():
108
+ print(dag_id, analysis.overall_severity)
109
+
110
+ for dag_id, failure in result.failures.items():
111
+ print(dag_id, failure)
112
+ ```
113
+
114
+ Batch results have their own versioned external contract. Raw exception objects
115
+ are converted to stable failure records containing `error_type` and `message`:
116
+
117
+ ```python
118
+ from flowsense import serialize_batch_analysis
119
+
120
+ payload = serialize_batch_analysis(result)
121
+ ```
122
+
123
+ Use `build_batch_analysis_document()` for a typed Pydantic document or
124
+ `batch_analysis_json_schema()` for the corresponding JSON Schema. The current
125
+ batch envelope schema version is `1.0`; each successful analysis retains its
126
+ own analysis schema version.
127
+
128
+ Concurrency is opt-in and bounded; `max_concurrency` defaults to `1`. Each DAG
129
+ receives its own context-managed data source. Expected `FlowSenseError`
130
+ instances are isolated in `failures`, while unexpected programming or adapter
131
+ errors still propagate to the caller. Use individual `AnalysisRequest` objects
132
+ when each DAG needs a different policy or historical run id. When concurrency
133
+ is greater than one, custom data-source factories and injected analysis engines
134
+ must be safe to call from multiple threads.
135
+
136
+ ## Custom data sources and engines
137
+
138
+ `FlowSenseClient` depends on the `DAGDataSourceFactory` and
139
+ `DAGAnalysisEngine` application ports. A new platform adapter can implement the
140
+ data-source protocol and supply a context-managed factory without depending on
141
+ Airflow. A complete alternative analysis workflow can be injected through the
142
+ optional `analysis_engine` constructor argument.