dirigent-examples 0.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. dirigent_examples/__init__.py +22 -0
  2. dirigent_examples/py.typed +0 -0
  3. dirigent_examples/shelves/README.md +299 -0
  4. dirigent_examples/shelves/composition/README.md +18 -0
  5. dirigent_examples/shelves/composition/chained-instances.yaml +97 -0
  6. dirigent_examples/shelves/composition/composition-child.yaml +64 -0
  7. dirigent_examples/shelves/composition/composition-parent.yaml +89 -0
  8. dirigent_examples/shelves/connections.yaml +52 -0
  9. dirigent_examples/shelves/demo/README.md +19 -0
  10. dirigent_examples/shelves/demo/markdown-showcase.yaml +117 -0
  11. dirigent_examples/shelves/demo/params-showcase.yaml +92 -0
  12. dirigent_examples/shelves/demo/requires.yaml +65 -0
  13. dirigent_examples/shelves/demo/weekly-import-malawi.yaml +54 -0
  14. dirigent_examples/shelves/demo/weekly-import-nepal.yaml +75 -0
  15. dirigent_examples/shelves/docker/README.md +29 -0
  16. dirigent_examples/shelves/docker/docker-build-push.yaml +110 -0
  17. dirigent_examples/shelves/docker/docker-build-run.yaml +106 -0
  18. dirigent_examples/shelves/docker/docker-compose-database.yaml +124 -0
  19. dirigent_examples/shelves/docker/docker-compose-failing-up.yaml +83 -0
  20. dirigent_examples/shelves/docker/docker-compose-file.yaml +117 -0
  21. dirigent_examples/shelves/docker/docker-compose-profiles-env.yaml +133 -0
  22. dirigent_examples/shelves/docker/docker-compose-stack.yaml +70 -0
  23. dirigent_examples/shelves/docker/docker-hello.yaml +53 -0
  24. dirigent_examples/shelves/docker/docker-remote-daemon.yaml +92 -0
  25. dirigent_examples/shelves/docker/docker-run-failing-teardown.yaml +94 -0
  26. dirigent_examples/shelves/docker/docker-ticker.yaml +49 -0
  27. dirigent_examples/shelves/execute/README.md +16 -0
  28. dirigent_examples/shelves/execute/long-log.yaml +89 -0
  29. dirigent_examples/shelves/failure/README.md +20 -0
  30. dirigent_examples/shelves/failure/error-handler.yaml +89 -0
  31. dirigent_examples/shelves/failure/optional-step.yaml +82 -0
  32. dirigent_examples/shelves/failure/retries.yaml +75 -0
  33. dirigent_examples/shelves/failure/retry-budget.yaml +82 -0
  34. dirigent_examples/shelves/failure/step-timeout.yaml +96 -0
  35. dirigent_examples/shelves/git/README.md +32 -0
  36. dirigent_examples/shelves/git/git-checkout-build.yaml +125 -0
  37. dirigent_examples/shelves/git/git-checkout-compose.yaml +138 -0
  38. dirigent_examples/shelves/git/git-checkout-public.yaml +84 -0
  39. dirigent_examples/shelves/graph/README.md +22 -0
  40. dirigent_examples/shelves/graph/deep-chain.yaml +119 -0
  41. dirigent_examples/shelves/graph/fan-in.yaml +80 -0
  42. dirigent_examples/shelves/graph/fan-out.yaml +66 -0
  43. dirigent_examples/shelves/graph/linear.yaml +66 -0
  44. dirigent_examples/shelves/graph/parallel-branches.yaml +57 -0
  45. dirigent_examples/shelves/graph/parallel-sleep.yaml +55 -0
  46. dirigent_examples/shelves/graph/skip-diamond.yaml +105 -0
  47. dirigent_examples/shelves/graph/wide-fan.yaml +147 -0
  48. dirigent_examples/shelves/hello-world.yaml +30 -0
  49. dirigent_examples/shelves/open-data/README.md +67 -0
  50. dirigent_examples/shelves/open-data/feeds-composition.yaml +120 -0
  51. dirigent_examples/shelves/open-data/gdacs-disaster-updates.yaml +230 -0
  52. dirigent_examples/shelves/open-data/github-releases-relay.yaml +225 -0
  53. dirigent_examples/shelves/open-data/hdx-dataset-watch.yaml +211 -0
  54. dirigent_examples/shelves/open-data/kobo-submissions-to-csv.yaml +149 -0
  55. dirigent_examples/shelves/open-data/nominatim-geocode-facilities.yaml +169 -0
  56. dirigent_examples/shelves/open-data/odk-central-submissions.yaml +146 -0
  57. dirigent_examples/shelves/open-data/open-meteo-weekly-report.yaml +142 -0
  58. dirigent_examples/shelves/open-data/overpass-health-facilities.yaml +154 -0
  59. dirigent_examples/shelves/open-data/usgs-earthquakes-alert.yaml +208 -0
  60. dirigent_examples/shelves/open-data/who-gho-indicators-to-parquet.yaml +163 -0
  61. dirigent_examples/shelves/open-data/wikidata-country-reference.yaml +146 -0
  62. dirigent_examples/shelves/open-data/world-bank-population-trend.yaml +166 -0
  63. dirigent_examples/shelves/patterns/README.md +144 -0
  64. dirigent_examples/shelves/patterns/concurrency-queue.yaml +89 -0
  65. dirigent_examples/shelves/patterns/concurrency-replace.yaml +92 -0
  66. dirigent_examples/shelves/patterns/concurrency-skip.yaml +97 -0
  67. dirigent_examples/shelves/patterns/connections-referenced-vs-carried.yaml +140 -0
  68. dirigent_examples/shelves/patterns/deadline-on-a-sensor.yaml +106 -0
  69. dirigent_examples/shelves/patterns/fan-out-continue.yaml +88 -0
  70. dirigent_examples/shelves/patterns/fan-out-fail-fast.yaml +80 -0
  71. dirigent_examples/shelves/patterns/fan-out-from-params.yaml +84 -0
  72. dirigent_examples/shelves/patterns/fan-out-item-wise.yaml +111 -0
  73. dirigent_examples/shelves/patterns/fan-out-literal-list.yaml +81 -0
  74. dirigent_examples/shelves/patterns/fan-out-nested-objects.yaml +107 -0
  75. dirigent_examples/shelves/patterns/fan-out-then-join.yaml +86 -0
  76. dirigent_examples/shelves/patterns/log-levels.yaml +119 -0
  77. dirigent_examples/shelves/patterns/outputs-inline-vs-storage.yaml +140 -0
  78. dirigent_examples/shelves/patterns/params-every-type.yaml +259 -0
  79. dirigent_examples/shelves/patterns/params-validation-refuses.yaml +131 -0
  80. dirigent_examples/shelves/patterns/pipeline-run-child.yaml +96 -0
  81. dirigent_examples/shelves/patterns/pipeline-run-fire-and-forget.yaml +99 -0
  82. dirigent_examples/shelves/patterns/pipeline-run-strict.yaml +103 -0
  83. dirigent_examples/shelves/patterns/pipeline-run-wait.yaml +107 -0
  84. dirigent_examples/shelves/patterns/pipeline-run-with-params.yaml +124 -0
  85. dirigent_examples/shelves/patterns/poll-cadence.yaml +102 -0
  86. dirigent_examples/shelves/patterns/priority-layered.yaml +120 -0
  87. dirigent_examples/shelves/patterns/references-cheat-sheet.yaml +186 -0
  88. dirigent_examples/shelves/patterns/retry-budget-exhausted.yaml +92 -0
  89. dirigent_examples/shelves/patterns/retry-exponential-backoff.yaml +88 -0
  90. dirigent_examples/shelves/patterns/retry-only-transient.yaml +108 -0
  91. dirigent_examples/shelves/patterns/retry-with-jitter.yaml +102 -0
  92. dirigent_examples/shelves/patterns/rule-all-done.yaml +83 -0
  93. dirigent_examples/shelves/patterns/rule-all-success.yaml +79 -0
  94. dirigent_examples/shelves/patterns/rule-always.yaml +92 -0
  95. dirigent_examples/shelves/patterns/rule-one-failed.yaml +86 -0
  96. dirigent_examples/shelves/patterns/schedule-at-once.yaml +110 -0
  97. dirigent_examples/shelves/patterns/schedule-cron-timezone.yaml +121 -0
  98. dirigent_examples/shelves/patterns/schedule-interval.yaml +109 -0
  99. dirigent_examples/shelves/patterns/schedule-window-half-open.yaml +105 -0
  100. dirigent_examples/shelves/patterns/sensor-http-ready.yaml +121 -0
  101. dirigent_examples/shelves/patterns/sensor-storage-exists.yaml +134 -0
  102. dirigent_examples/shelves/patterns/step-names-and-keys.yaml +99 -0
  103. dirigent_examples/shelves/patterns/timeout-fails-the-step.yaml +94 -0
  104. dirigent_examples/shelves/patterns/timeout-skips-the-step.yaml +102 -0
  105. dirigent_examples/shelves/patterns/webhook-mapping-nested-payload.yaml +125 -0
  106. dirigent_examples/shelves/patterns/webhook-signed.yaml +144 -0
  107. dirigent_examples/shelves/preview/s3-parquet-to-ingestion.yaml +92 -0
  108. dirigent_examples/shelves/python/README.md +31 -0
  109. dirigent_examples/shelves/python/apply_and_run.py +52 -0
  110. dirigent_examples/shelves/python/ci_gate.py +76 -0
  111. dirigent_examples/shelves/python/connections.py +61 -0
  112. dirigent_examples/shelves/python/error_handling.py +84 -0
  113. dirigent_examples/shelves/python/follow_logs.py +39 -0
  114. dirigent_examples/shelves/python/list_and_filter.py +52 -0
  115. dirigent_examples/shelves/queues/README.md +59 -0
  116. dirigent_examples/shelves/queues/kafka-consume-then-transform.yaml +105 -0
  117. dirigent_examples/shelves/queues/kafka-produce-then-consume.yaml +124 -0
  118. dirigent_examples/shelves/queues/rabbitmq-consume-ack-on-success.yaml +102 -0
  119. dirigent_examples/shelves/queues/report-to-kafka.yaml +105 -0
  120. dirigent_examples/shelves/queues/report-to-rabbitmq.yaml +105 -0
  121. dirigent_examples/shelves/recipes/README.md +130 -0
  122. dirigent_examples/shelves/recipes/csv-header-rules.yaml +147 -0
  123. dirigent_examples/shelves/recipes/csv-to-ndjson.yaml +107 -0
  124. dirigent_examples/shelves/recipes/etl-csv-clean-validate-parquet.yaml +207 -0
  125. dirigent_examples/shelves/recipes/filter-by-predicate.yaml +116 -0
  126. dirigent_examples/shelves/recipes/filter-then-map-then-reduce.yaml +109 -0
  127. dirigent_examples/shelves/recipes/http-fetch-validate-post.yaml +142 -0
  128. dirigent_examples/shelves/recipes/http-follow-redirects.yaml +100 -0
  129. dirigent_examples/shelves/recipes/http-get-with-query.yaml +96 -0
  130. dirigent_examples/shelves/recipes/http-headers-and-auth-connection.yaml +110 -0
  131. dirigent_examples/shelves/recipes/http-post-file-from-storage.yaml +117 -0
  132. dirigent_examples/shelves/recipes/http-post-json-echo.yaml +105 -0
  133. dirigent_examples/shelves/recipes/http-post-report.yaml +183 -0
  134. dirigent_examples/shelves/recipes/http-save-body-to-storage.yaml +106 -0
  135. dirigent_examples/shelves/recipes/http-success-status-list.yaml +80 -0
  136. dirigent_examples/shelves/recipes/http-timeout-override.yaml +104 -0
  137. dirigent_examples/shelves/recipes/jq-dedupe-by-key.yaml +78 -0
  138. dirigent_examples/shelves/recipes/jq-defaults-and-nulls.yaml +91 -0
  139. dirigent_examples/shelves/recipes/jq-group-by-and-sum.yaml +74 -0
  140. dirigent_examples/shelves/recipes/jq-join-two-lists.yaml +77 -0
  141. dirigent_examples/shelves/recipes/jq-long-to-wide.yaml +76 -0
  142. dirigent_examples/shelves/recipes/jq-nested-to-flat.yaml +89 -0
  143. dirigent_examples/shelves/recipes/jq-pivot-wide-to-long.yaml +65 -0
  144. dirigent_examples/shelves/recipes/jq-running-totals.yaml +82 -0
  145. dirigent_examples/shelves/recipes/jq-string-cleaning.yaml +88 -0
  146. dirigent_examples/shelves/recipes/jq-top-n.yaml +85 -0
  147. dirigent_examples/shelves/recipes/jq-validate-in-jq-vs-schema.yaml +124 -0
  148. dirigent_examples/shelves/recipes/jq-window-dates.yaml +82 -0
  149. dirigent_examples/shelves/recipes/json-to-csv-flattening.yaml +141 -0
  150. dirigent_examples/shelves/recipes/large-output-to-storage.yaml +134 -0
  151. dirigent_examples/shelves/recipes/map-enrich-with-lookup.yaml +96 -0
  152. dirigent_examples/shelves/recipes/ndjson-to-parquet.yaml +130 -0
  153. dirigent_examples/shelves/recipes/pagination-by-fan-out.yaml +115 -0
  154. dirigent_examples/shelves/recipes/parquet-round-trip-types.yaml +163 -0
  155. dirigent_examples/shelves/recipes/reconcile-two-sources.yaml +159 -0
  156. dirigent_examples/shelves/recipes/report-built-in.yaml +72 -0
  157. dirigent_examples/shelves/recipes/report-daily-digest.yaml +186 -0
  158. dirigent_examples/shelves/recipes/report-to-file.yaml +131 -0
  159. dirigent_examples/shelves/recipes/report-to-webhook.yaml +136 -0
  160. dirigent_examples/shelves/recipes/schema-carried.yaml +112 -0
  161. dirigent_examples/shelves/recipes/schema-formats.yaml +107 -0
  162. dirigent_examples/shelves/recipes/schema-referenced.yaml +86 -0
  163. dirigent_examples/shelves/recipes/schema-refuses-then-rule.yaml +127 -0
  164. dirigent_examples/shelves/recipes/storage-copy-dated-archive.yaml +114 -0
  165. dirigent_examples/shelves/recipes/storage-exists-gate.yaml +127 -0
  166. dirigent_examples/shelves/recipes/storage-manifest-of-a-fan-out.yaml +104 -0
  167. dirigent_examples/shelves/recipes/storage-write-then-read.yaml +119 -0
  168. dirigent_examples/shelves/recipes/webhook-post-hmac.yaml +132 -0
  169. dirigent_examples/shelves/recipes/webhook-post-summary.yaml +142 -0
  170. dirigent_examples/shelves/s3/README.md +34 -0
  171. dirigent_examples/shelves/s3/report-to-s3.yaml +93 -0
  172. dirigent_examples/shelves/s3/s3-copy-and-verify.yaml +105 -0
  173. dirigent_examples/shelves/s3/s3-csv-report.yaml +87 -0
  174. dirigent_examples/shelves/s3/s3-parquet-report.yaml +77 -0
  175. dirigent_examples/shelves/s3/s3-round-trip.yaml +125 -0
  176. dirigent_examples/shelves/schemas/README.md +36 -0
  177. dirigent_examples/shelves/schemas/echo-reading.json +18 -0
  178. dirigent_examples/shelves/schemas/ou-record.json +13 -0
  179. dirigent_examples/shelves/schemas/station-reading.json +13 -0
  180. dirigent_examples/shelves/sensors/README.md +16 -0
  181. dirigent_examples/shelves/sensors/sensor-gate.yaml +65 -0
  182. dirigent_examples/shelves/sensors/time-window.yaml +61 -0
  183. dirigent_examples/shelves/sql/README.md +52 -0
  184. dirigent_examples/shelves/sql/duckdb-parquet-to-report.yaml +146 -0
  185. dirigent_examples/shelves/sql/sql-postgres-readonly.yaml +111 -0
  186. dirigent_examples/shelves/sql/sql-query-to-storage.yaml +85 -0
  187. dirigent_examples/shelves/sql/sql-sqlite-roundtrip.yaml +114 -0
  188. dirigent_examples/shelves/sql/warehouse.sql +42 -0
  189. dirigent_examples/shelves/transform/README.md +36 -0
  190. dirigent_examples/shelves/transform/csv-report.yaml +55 -0
  191. dirigent_examples/shelves/transform/jq-filter-and-map.yaml +70 -0
  192. dirigent_examples/shelves/transform/jq-group-and-aggregate.yaml +70 -0
  193. dirigent_examples/shelves/transform/jq-join-two-sources.yaml +98 -0
  194. dirigent_examples/shelves/transform/jq-reshape.yaml +91 -0
  195. dirigent_examples/shelves/transform/jq-stream-through-storage.yaml +112 -0
  196. dirigent_examples/shelves/transform/ndjson-round-trip.yaml +56 -0
  197. dirigent_examples/shelves/transform/parquet-round-trip.yaml +68 -0
  198. dirigent_examples/shelves/transform/std-convert-fan-out.yaml +142 -0
  199. dirigent_examples/shelves/transform/xml-feed-to-ndjson.yaml +116 -0
  200. dirigent_examples/shelves/transform/yaml-config-to-json.yaml +104 -0
  201. dirigent_examples/shelves/triggers/README.md +45 -0
  202. dirigent_examples/shelves/triggers/at-one-time.yaml +78 -0
  203. dirigent_examples/shelves/triggers/cron-nightly.yaml +79 -0
  204. dirigent_examples/shelves/triggers/cron-windowed.yaml +86 -0
  205. dirigent_examples/shelves/triggers/document-nightly.yaml +80 -0
  206. dirigent_examples/shelves/triggers/interval-rolling.yaml +88 -0
  207. dirigent_examples/shelves/triggers/managed-and-manual.yaml +109 -0
  208. dirigent_examples/shelves/triggers/webhook-trigger.yaml +75 -0
  209. dirigent_examples/shelves/validate/README.md +31 -0
  210. dirigent_examples/shelves/validate/expects-a-shape.yaml +56 -0
  211. dirigent_examples/shelves/validate/the-shape-is-wrong.yaml +46 -0
  212. dirigent_examples-0.15.0.dist-info/METADATA +21 -0
  213. dirigent_examples-0.15.0.dist-info/RECORD +216 -0
  214. dirigent_examples-0.15.0.dist-info/WHEEL +4 -0
  215. dirigent_examples-0.15.0.dist-info/entry_points.txt +3 -0
  216. dirigent_examples-0.15.0.dist-info/licenses/LICENSE +18 -0
@@ -0,0 +1,80 @@
1
+ # Fan-in: reading a whole fan-out back as one list, in one downstream step.
2
+ #
3
+ # fan-out.yaml maps a step over a list. This is the other half: what the step *after* it
4
+ # sees. Three rules, and they are the whole of it:
5
+ #
6
+ # 1. A fan-out step's output is the list of its items' outputs, in item order.
7
+ # ${steps.fetch.output} is a JSON array here, not an object.
8
+ # 2. The downstream step runs ONCE, not once per item. It has no for_each of its own, so
9
+ # it is one attempt that reads the whole batch.
10
+ # 3. Only items that SUCCEEDED are in that list. Under items: continue a failed item is
11
+ # absent rather than null, so the list can be shorter than the input, and index 0 is
12
+ # the first item that worked rather than the first one asked for.
13
+ #
14
+ # The list can be indexed: ${steps.fetch.output.0.status} is the integer 200. Bear rule 3
15
+ # in mind before using it -- with one failure upstream, index 0 is a different dataset than
16
+ # it was yesterday. Reading the whole list is the honest form.
17
+ #
18
+ # Note the deliberate asymmetry with the CLI, where `-p regions.0=east` is refused. Reading
19
+ # an existing structure by index is unambiguous: the value is there, and it is either a list
20
+ # or it is not. Writing into one that may not exist is not: a dotted path cannot tell the
21
+ # index 0 from an object key named "0", and cannot say how long the array should be.
22
+ #
23
+ # Two edges worth knowing. A fan-out that tolerated a failure still counts as succeeded, so
24
+ # the default rule: all_success runs this step; the run ends completed_with_errors. But if
25
+ # *every* item fails the step itself failed, and a downstream step forced to run anyway
26
+ # with rule: all_done is refused -- there is no stored output to read, and the attempt
27
+ # settles as rejected rather than silently receiving an empty list.
28
+ #
29
+ # One block, used twice, and it runs no code on the worker, so this one needs no allowlist:
30
+ #
31
+ # dg run --local examples/graph/fan-in.yaml
32
+ # dg run --local examples/graph/fan-in.yaml -p datasets='["temperature","humidity"]'
33
+ #
34
+ # The latency point: four slow fetches cost four parked rows and nothing else. The engine
35
+ # is not holding four workers for the duration; it is holding four rows in the database.
36
+
37
+ format: dirigent/v1
38
+ kind: pipeline
39
+ code: fan-in
40
+ name: Branches that join
41
+ description: Fetch several datasets in parallel, then send the whole batch on in one call.
42
+
43
+ tags: [graph, http]
44
+
45
+ requires:
46
+ blocks:
47
+ - http.request
48
+
49
+ params:
50
+ type: object
51
+ properties:
52
+ datasets:
53
+ type: array
54
+ default: [temperature, humidity, pressure, wind]
55
+ items:
56
+ type: string
57
+
58
+ steps:
59
+ fetch:
60
+ block: http.request
61
+ for_each: "${params.datasets}"
62
+ items: continue
63
+ config:
64
+ url: https://postman-echo.com/get
65
+ method: GET
66
+ query:
67
+ dataset: "${item}"
68
+
69
+ report:
70
+ block: http.request
71
+ depends_on: [fetch]
72
+ config:
73
+ url: https://postman-echo.com/post
74
+ method: POST
75
+ body:
76
+ # A fan-out step's output is the list of its elements' outputs, in the order the
77
+ # elements were created, so one reference collects the whole fan and an index
78
+ # reaches one element of it.
79
+ collected: "${steps.fetch.output}"
80
+ first_status: "${steps.fetch.output.0.status}"
@@ -0,0 +1,66 @@
1
+ # Fan-out: one step mapped over a list, with one run item per element.
2
+ #
3
+ # for_each is expanded when the run is created, so the item grid exists from the moment the
4
+ # run is visible. It may read params, run, and item -- but not another step's output,
5
+ # because the cardinality has to be known before anything executes.
6
+ #
7
+ # items: continue is the item policy: a region that fails does not stop the others, its
8
+ # failure is recorded against its own run item, and the run reports completed_with_errors
9
+ # rather than failed. Under the default, fail_fast, any failed item fails the whole step.
10
+ #
11
+ # What a failed item costs downstream: the step still counts as succeeded, so an ordinary
12
+ # all_success edge out of it runs. But the step's output is the list of its items' outputs
13
+ # and a failed item is absent from it -- not null, absent -- so a step reading the batch
14
+ # sees only what worked. If every item fails the step failed, and then the edge is skipped.
15
+ # examples/graph/fan-in.yaml is that downstream step.
16
+ #
17
+ # This one uses shell.run, which executes code on the worker and is refused unless the
18
+ # instance allowlists it: export DIRIGENT_ENABLED_UNSAFE_BLOCKS='["shell.run"]', or pass
19
+ # --enable-unsafe shell.run to a local run.
20
+ #
21
+ # dg run --local examples/graph/fan-out.yaml -p day=2026-01-01
22
+ # dg run --local examples/graph/fan-out.yaml -p regions='["east","west","north","south"]'
23
+
24
+ format: dirigent/v1
25
+ kind: pipeline
26
+ code: fan-out
27
+ name: One step fans out
28
+ description: Push one payload per region, tolerating a region that refuses it.
29
+
30
+ tags: [graph, execute, http]
31
+
32
+ params:
33
+ type: object
34
+ required: [day]
35
+ properties:
36
+ day:
37
+ type: string
38
+ format: date
39
+ regions:
40
+ type: array
41
+ default: [east, west, north]
42
+ items:
43
+ type: string
44
+
45
+ steps:
46
+ push:
47
+ block: http.request
48
+ for_each: "${params.regions}"
49
+ # One element per region, each its own attempt. items: continue lets the rest finish
50
+ # when one region refuses; items: halt would stop the fan-out at the first failure.
51
+ items: continue
52
+ config:
53
+ url: https://postman-echo.com/post
54
+ method: POST
55
+ body:
56
+ day: "${params.day}"
57
+ region: "${item}"
58
+
59
+ audit:
60
+ block: shell.run
61
+ depends_on: [push]
62
+ # all_done runs the audit once every element has settled, failures included. The
63
+ # default all_success would skip it precisely when there is something to audit.
64
+ rule: all_done
65
+ config:
66
+ argv: [echo, "the push step has settled for every region"]
@@ -0,0 +1,66 @@
1
+ # A four-step chain, and how one step reads another's output.
2
+ #
3
+ # depends_on draws the edge; ${steps.<name>.output.<field>} reads across it. A step may
4
+ # only read from a step it depends on, transitively, and apply checks that: the reference
5
+ # language has no way to reach sideways into an unrelated branch.
6
+ #
7
+ # The answer travels as a value in the fetch step's output, and storage.write is what puts
8
+ # it in the run's scratch space, so the copy step has a real object to move.
9
+ #
10
+ # report uses shell.run, which executes code on the worker and is refused unless the
11
+ # instance allowlists it:
12
+ # export DIRIGENT_ENABLED_UNSAFE_BLOCKS='["shell.run"]'
13
+ # dg run --local examples/graph/linear.yaml -p day=2026-01-01
14
+ # or, without touching the environment:
15
+ # dg run --local examples/graph/linear.yaml -p day=2026-01-01 --enable-unsafe shell.run
16
+
17
+ format: dirigent/v1
18
+ kind: pipeline
19
+ code: linear
20
+ name: A straight line of steps
21
+ description: Fetch a payload, store it, archive the object, then report what was archived.
22
+
23
+ tags: [graph, execute, http, storage]
24
+
25
+ params:
26
+ type: object
27
+ required: [day]
28
+ properties:
29
+ day:
30
+ type: string
31
+ format: date
32
+
33
+ steps:
34
+ fetch:
35
+ block: http.request
36
+ config:
37
+ url: https://postman-echo.com/get
38
+ method: GET
39
+ query:
40
+ day: "${params.day}"
41
+
42
+ store:
43
+ block: storage.write
44
+ depends_on: [fetch]
45
+ config:
46
+ # storage.write is the only way a value leaves the run, so the answer the fetch
47
+ # carries becomes an object here and nowhere else.
48
+ target: "${run.scratch}/incoming/${params.day}.json"
49
+ value: "${steps.fetch.output.body}"
50
+
51
+ archive:
52
+ block: storage.copy
53
+ # depends_on is what orders the run; nothing infers an edge from one step reading
54
+ # another's output, so the dependency is stated even though the reference implies it.
55
+ depends_on: [store]
56
+ config:
57
+ source: "${steps.store.output.uri}"
58
+ target: "${run.scratch}/archive/${params.day}.json"
59
+
60
+ report:
61
+ block: shell.run
62
+ depends_on: [archive]
63
+ config:
64
+ argv:
65
+ - echo
66
+ - "service ${steps.fetch.output.status}, archived ${steps.archive.output.bytes_copied} bytes"
@@ -0,0 +1,57 @@
1
+ # A diamond: one root, two independent branches, one join.
2
+ #
3
+ # Parallelism is implicit. Nothing declares that read_side and write_side run at the same
4
+ # time; they simply have no edge between them, so both become claimable the moment their
5
+ # shared prerequisite succeeds and whichever workers are free pick them up.
6
+ #
7
+ # The join uses the default rule, all_success, so summarise runs only if both branches
8
+ # succeeded. Change it to all_done and it would run regardless.
9
+ #
10
+ # This one uses shell.run, which executes code on the worker and is refused unless the
11
+ # instance allowlists it: export DIRIGENT_ENABLED_UNSAFE_BLOCKS='["shell.run"]', or pass
12
+ # --enable-unsafe shell.run to a local run.
13
+ #
14
+ # dg run --local examples/graph/parallel-branches.yaml
15
+
16
+ format: dirigent/v1
17
+ kind: pipeline
18
+ code: parallel-branches
19
+ name: Branches in parallel
20
+ description: Call two endpoints in parallel and summarise both results.
21
+
22
+ tags: [graph, execute, http]
23
+
24
+ steps:
25
+ prepare:
26
+ block: shell.run
27
+ config:
28
+ argv: [echo, "starting both branches"]
29
+
30
+ read_side:
31
+ block: http.request
32
+ depends_on: [prepare]
33
+ config:
34
+ url: https://postman-echo.com/get
35
+ method: GET
36
+ query:
37
+ branch: read
38
+
39
+ write_side:
40
+ block: http.request
41
+ # Both branches depend on prepare and on nothing else, which is the whole of what makes
42
+ # them parallel: the engine runs whatever is claimable, and nothing here orders these two.
43
+ depends_on: [prepare]
44
+ config:
45
+ url: https://postman-echo.com/post
46
+ method: POST
47
+ body:
48
+ branch: write
49
+
50
+ summarise:
51
+ block: shell.run
52
+ depends_on: [read_side, write_side]
53
+ rule: all_success
54
+ config:
55
+ argv:
56
+ - echo
57
+ - "read ${steps.read_side.output.status}, write ${steps.write_side.output.status}"
@@ -0,0 +1,55 @@
1
+ # Fan-out and fan-in with no fan-out syntax at all: four plain steps, drawn by their edges.
2
+ #
3
+ # The three sleeps declare no depends_on, so they are all roots, and every root is claimable
4
+ # the moment the run is created. Nothing serialises them but worker concurrency: what bounds
5
+ # this run is how many block calls a worker carries at a time, not the shape of the DAG. Run
6
+ # serially the waits would take nine seconds, and this run takes about five. Time it -- the
7
+ # example proves its own claim.
8
+ #
9
+ # report depends on all three, and the default trigger rule is all_success, so it waits for
10
+ # every one of them and reads each one's output. That is the join: no barrier step, no
11
+ # gather syntax, just an edge from each sleep.
12
+ #
13
+ # time.sleep is a sensor, so none of those seconds occupy a worker: each poke reads the clock
14
+ # and returns, and the wait is a parked row the engine wakes later. The same shape with
15
+ # shell.run and sleep would hold a worker slot for the whole wait.
16
+ #
17
+ # The asymmetry is worth seeing: the sleeps need no allowlist, and report does, because
18
+ # shell.run executes code on the worker and is refused unless the instance allows it.
19
+ #
20
+ # time dg run --local examples/graph/parallel-sleep.yaml --enable-unsafe shell.run
21
+
22
+ format: dirigent/v1
23
+ kind: pipeline
24
+ code: parallel-sleep
25
+ name: Sleeps in parallel
26
+ description: Three independent waits that run at once, and one step that waits for all of them.
27
+
28
+ tags: [graph, execute, sensor]
29
+
30
+ steps:
31
+ # None of the three sleeps depends on another, so they are claimable at once and the run
32
+ # takes as long as the longest rather than as long as the sum.
33
+ slow:
34
+ block: time.sleep
35
+ config:
36
+ for: 5s
37
+
38
+ medium:
39
+ block: time.sleep
40
+ config:
41
+ for: 3s
42
+
43
+ quick:
44
+ block: time.sleep
45
+ config:
46
+ for: 1s
47
+
48
+ report:
49
+ block: shell.run
50
+ depends_on: [slow, medium, quick]
51
+ config:
52
+ argv:
53
+ - echo
54
+ - "3 steps completed slow=${steps.slow.output.waited_ms}ms
55
+ medium=${steps.medium.output.waited_ms}ms quick=${steps.quick.output.waited_ms}ms"
@@ -0,0 +1,105 @@
1
+ # The happy path, where the error handler is skipped and the run is green anyway.
2
+ #
3
+ # error-handler.yaml runs the failure path: the load fails, the one_failed branch fires. This
4
+ # is the same diamond taking the other road, and it exists because a skipped step is the
5
+ # outcome people misread most often.
6
+ #
7
+ # A skip is not a failure. It is not a step that did nothing, either, and it is not an error
8
+ # suppressed. It is the engine reporting that an edge condition was evaluated and came out
9
+ # false, which is an ordinary, correct, green thing for a run to contain. A run whose alert
10
+ # branch was skipped is a run where nothing needed alerting about.
11
+ #
12
+ # Four steps, four outcomes worth reading off the graph as written:
13
+ #
14
+ # verify succeeds the load is healthy
15
+ # raise_alarm SKIPPED rule: one_failed, and nothing failed, so the edge is false
16
+ # record_success succeeds rule: all_success, and the edge is true
17
+ # close_out succeeds rule: all_done, which is satisfied by a skip as much as by a
18
+ # success -- this is why cleanup hangs off all_done and never
19
+ # off all_success, which would wait on a step that never ran
20
+ #
21
+ # Flip it with the parameter and the same four steps swap two of those outcomes: raise_alarm
22
+ # runs, record_success is skipped, close_out still runs. Same document, same edges, no
23
+ # branching syntax anywhere -- only the outcome of one step changed.
24
+ #
25
+ # One caution the graph does not show. one_failed reads the outcome a dependent sees, and a
26
+ # step marked continue_on_failure: true shows its dependents a success even when it failed.
27
+ # So an error handler hung under a tolerated step never fires. Tolerating a failure and
28
+ # handling one are opposite instructions; optional-step.yaml is the one that tolerates.
29
+ #
30
+ # This one uses shell.run, which executes code on the worker and is refused unless the
31
+ # instance allowlists it: export DIRIGENT_ENABLED_UNSAFE_BLOCKS='["shell.run"]', or pass
32
+ # --enable-unsafe shell.run to a local run.
33
+ #
34
+ # dg run --local examples/graph/skip-diamond.yaml --enable-unsafe shell.run # green, one skip
35
+ # dg run --local examples/graph/skip-diamond.yaml --enable-unsafe shell.run -p fail=true # the other road
36
+
37
+ format: dirigent/v1
38
+ kind: pipeline
39
+ code: skip-diamond
40
+ name: Skip diamond
41
+ description: |
42
+ A load that works, an alarm that is therefore skipped, and a close-out that runs either way.
43
+
44
+ **A skip is not a failure.** It is an edge condition that evaluated false, and a run
45
+ full of them can be perfectly green.
46
+
47
+ | Step | Rule | On the happy path |
48
+ | --- | --- | --- |
49
+ | `verify` | -- | succeeds |
50
+ | `raise_alarm` | `one_failed` | skipped |
51
+ | `record_success` | `all_success` | succeeds |
52
+ | `close_out` | `all_done` | succeeds |
53
+
54
+ Pass `-p fail=true` and the middle two swap places. `close_out` does not move, which is
55
+ the whole reason cleanup hangs off `all_done`.
56
+
57
+ tags: [graph, execute]
58
+
59
+ requires:
60
+ blocks:
61
+ - shell.run
62
+
63
+ params:
64
+ type: object
65
+ properties:
66
+ fail:
67
+ type: boolean
68
+ description: Make the verification fail, to take the branch this example does not.
69
+ default: false
70
+
71
+ steps:
72
+ verify:
73
+ block: shell.run
74
+ config:
75
+ # Exits 0 unless asked otherwise, so the default run takes the road where the handler
76
+ # is skipped rather than the one error-handler.yaml already shows.
77
+ command: "if [ '${params.fail}' = 'true' ]; then echo 'checksum mismatch' >&2; exit 1; fi; echo 'verified 412 rows'"
78
+
79
+ raise_alarm:
80
+ block: shell.run
81
+ depends_on: [verify]
82
+ # Skipped on the happy path. Nothing failed, so there is nothing for a handler to handle,
83
+ # and the engine says so in the outcome rather than running it and having it decide.
84
+ rule: one_failed
85
+ config:
86
+ argv: [echo, "alarm raised: verification failed"]
87
+
88
+ record_success:
89
+ block: shell.run
90
+ depends_on: [verify]
91
+ # The default rule, written out here only because its opposite is directly above it.
92
+ rule: all_success
93
+ config:
94
+ argv: [echo, "recorded a clean verification"]
95
+
96
+ close_out:
97
+ block: shell.run
98
+ depends_on: [raise_alarm, record_success]
99
+ # Exactly one of the two above ran and the other was skipped, and all_done is satisfied by
100
+ # both -- a skip is settled. all_success here would itself be skipped: a skipped
101
+ # prerequisite can never satisfy it, so the skip would propagate down and this step would
102
+ # never run -- the mistake this step exists to not make.
103
+ rule: all_done
104
+ config:
105
+ argv: [echo, "closed out, whichever road was taken"]
@@ -0,0 +1,147 @@
1
+ # Graph width: one start, eight branches that are all claimable at once, and one join.
2
+ #
3
+ # This is deep-chain.yaml turned ninety degrees. There the ten steps drew a column and the
4
+ # run could not go faster than one worker; here the eight branches draw a row, every one of
5
+ # them depends only on the split, and the run goes exactly as fast as the workers allow.
6
+ #
7
+ # Width is not a keyword. Nothing here declares parallelism: the eight branches are parallel
8
+ # because none of them names another in depends_on, and that is the whole mechanism. The
9
+ # engine claims every step whose prerequisites have settled, so all eight become claimable in
10
+ # the same instant the split settles. What actually bounds them is worker concurrency -- the
11
+ # number of block calls a worker carries at a time -- which is an instance setting and not
12
+ # something a document gets a say in.
13
+ #
14
+ # collect is the join. It names all eight, and the default rule is all_success, so it waits
15
+ # for every one of them before it runs once. That is the same shape parallel-sleep.yaml draws
16
+ # with three sensors; the difference is that this one has a common start above the fan, which
17
+ # makes it a proper diamond rather than eight roots.
18
+ #
19
+ # Nothing here reaches the network or runs code on the worker, so it needs no allowlist.
20
+ #
21
+ # dg run --local examples/graph/wide-fan.yaml
22
+ # dg run --local examples/graph/wide-fan.yaml -p factor=10
23
+
24
+ format: dirigent/v1
25
+ kind: pipeline
26
+ code: wide-fan
27
+ name: Wide fan
28
+ description: |
29
+ One step splits, eight run at once, one joins them back.
30
+
31
+ A DAG's **width** is how many steps are claimable at the same moment, and it is what a
32
+ run demands of the workers. Its opposite is `deep-chain`, where the same ten steps stand
33
+ in a single line and no worker count helps.
34
+
35
+ Nothing declares the parallelism. The eight branches are parallel because none of them
36
+ names another.
37
+
38
+ tags: [graph, transform]
39
+
40
+ requires:
41
+ blocks:
42
+ - transform.jq
43
+
44
+ params:
45
+ type: object
46
+ properties:
47
+ factor:
48
+ type: integer
49
+ description: What each branch multiplies its own region weight by.
50
+ default: 2
51
+
52
+ steps:
53
+ # The single root. Everything below reads it, so nothing is claimable until it settles --
54
+ # which is what makes the eight start together rather than trickling.
55
+ split:
56
+ block: transform.jq
57
+ config:
58
+ input:
59
+ factor: "${params.factor}"
60
+ program: "{factor: .factor, regions: 8}"
61
+
62
+ # Eight separate steps rather than one for_each, because this example is about the shape of
63
+ # the graph. fan-out.yaml is the same parallelism expressed as one step over a list, and
64
+ # that is what a real pipeline over eight regions would use.
65
+ bagmati:
66
+ block: transform.jq
67
+ depends_on: [split]
68
+ config:
69
+ input: "${steps.split.output.value}"
70
+ program: "{region: \"bagmati\", weight: (.factor * 3)}"
71
+
72
+ gandaki:
73
+ block: transform.jq
74
+ depends_on: [split]
75
+ config:
76
+ input: "${steps.split.output.value}"
77
+ program: "{region: \"gandaki\", weight: (.factor * 5)}"
78
+
79
+ karnali:
80
+ block: transform.jq
81
+ depends_on: [split]
82
+ config:
83
+ input: "${steps.split.output.value}"
84
+ program: "{region: \"karnali\", weight: (.factor * 7)}"
85
+
86
+ koshi:
87
+ block: transform.jq
88
+ depends_on: [split]
89
+ config:
90
+ input: "${steps.split.output.value}"
91
+ program: "{region: \"koshi\", weight: (.factor * 11)}"
92
+
93
+ lumbini:
94
+ block: transform.jq
95
+ depends_on: [split]
96
+ config:
97
+ input: "${steps.split.output.value}"
98
+ program: "{region: \"lumbini\", weight: (.factor * 13)}"
99
+
100
+ madhesh:
101
+ block: transform.jq
102
+ depends_on: [split]
103
+ config:
104
+ input: "${steps.split.output.value}"
105
+ program: "{region: \"madhesh\", weight: (.factor * 17)}"
106
+
107
+ sudurpashchim:
108
+ block: transform.jq
109
+ depends_on: [split]
110
+ config:
111
+ input: "${steps.split.output.value}"
112
+ program: "{region: \"sudurpashchim\", weight: (.factor * 19)}"
113
+
114
+ national:
115
+ block: transform.jq
116
+ depends_on: [split]
117
+ config:
118
+ input: "${steps.split.output.value}"
119
+ program: "{region: \"national\", weight: (.factor * 23)}"
120
+
121
+ # The join. Eight edges in, one attempt, and every branch's output read by name. There is
122
+ # no gather syntax and no barrier step: the edges are the join.
123
+ collect:
124
+ block: transform.jq
125
+ depends_on:
126
+ - bagmati
127
+ - gandaki
128
+ - karnali
129
+ - koshi
130
+ - lumbini
131
+ - madhesh
132
+ - sudurpashchim
133
+ - national
134
+ config:
135
+ input:
136
+ - "${steps.bagmati.output.value}"
137
+ - "${steps.gandaki.output.value}"
138
+ - "${steps.karnali.output.value}"
139
+ - "${steps.koshi.output.value}"
140
+ - "${steps.lumbini.output.value}"
141
+ - "${steps.madhesh.output.value}"
142
+ - "${steps.sudurpashchim.output.value}"
143
+ - "${steps.national.output.value}"
144
+ program: |
145
+ {branches: length,
146
+ total_weight: (map(.weight) | add),
147
+ heaviest: (max_by(.weight) | .region)}
@@ -0,0 +1,30 @@
1
+ # The smallest thing dirigent can run: one step, one block, no parameters.
2
+ #
3
+ # value.const emits its configured value and touches nothing, so this runs on a fresh
4
+ # instance with nothing on the unsafe allowlist:
5
+ #
6
+ # dg apply examples/hello-world.yaml
7
+ # dg run hello-world --watch
8
+
9
+ format: dirigent/v1
10
+ kind: pipeline
11
+ code: hello-world
12
+ # The code above is the identity: it is what a URL, a reference and `dg run` carry.
13
+ # The name below is display only -- free-form, and nothing ever references by it.
14
+ name: Hello, world
15
+ description: |
16
+ Emit a greeting, and nothing else.
17
+
18
+ This is the **smallest** document the format admits: one step, one block, no
19
+ parameters. A description is markdown, so the UI renders it as prose:
20
+
21
+ - `value.const` emits its config as its output, with nothing granted
22
+ - everything else here is a default
23
+
24
+ tags: [demo]
25
+
26
+ steps:
27
+ greet:
28
+ block: value.const
29
+ config:
30
+ value: "hello from dirigent"
@@ -0,0 +1,67 @@
1
+ # Open data
2
+
3
+ Real pipelines against real public systems. Every source on this shelf is a live, public API,
4
+ and every one of them but two is keyless: no account, no token, no registration, nothing to
5
+ stand up. `dg run --local examples/open-data/<file>.yaml` reaches the internet and comes back
6
+ with today's data.
7
+
8
+ The two exceptions say so in their headers and in the table below. `kobo-submissions-to-csv`
9
+ needs an account token, because the submissions are somebody's household survey and there is no
10
+ anonymous read; `odk-central-submissions` needs a server and an account for the same reason.
11
+ They are here because a shelf of only-open sources would never show where a credential belongs
12
+ -- in a connection the instance holds, not in the document.
13
+
14
+ That distinction is what "it runs" means on each row. Every keyless document here has been run
15
+ end to end against its live source and ended `succeeded` with real data;
16
+ `gdacs-disaster-updates` was run twice under one `dg run --local --root`, a day apart, to see
17
+ both the empty first day and the second day's difference. The two credentialed ones stop where the credential does: their
18
+ documents validate and their graphs are the same shape, but nobody's Kobo token or ODK Central
19
+ server is in this repository, so the request itself is what a reader has to supply.
20
+
21
+ Nothing here uses a block outside the core catalog, and nothing runs code on a worker: no
22
+ `shell.run`, no allowlist entry, no `--enable-unsafe`. Reshaping is jq, encoding is a codec,
23
+ waiting is a sensor.
24
+
25
+ | File | What it teaches |
26
+ | --- | --- |
27
+ | [open-meteo-weekly-report.yaml](open-meteo-weekly-report.yaml) | A windowed weekly schedule, a columnar API transposed to rows, and csv written under the window's start |
28
+ | [who-gho-indicators-to-parquet.yaml](who-gho-indicators-to-parquet.yaml) | A fan-out read back as one list, flattened into one table, written as one parquet file, and a manifest that counts what landed |
29
+ | [world-bank-population-trend.yaml](world-bank-population-trend.yaml) | Paging made visible: an envelope checked with `error()`, year-on-year arithmetic in jq, and a carried schema gating the rows |
30
+ | [usgs-earthquakes-alert.yaml](usgs-earthquakes-alert.yaml) | Haversine in jq, thresholds carried as data rather than spliced into a program, and posting **only** when something matched |
31
+ | [overpass-health-facilities.yaml](overpass-health-facilities.yaml) | A query language in a query parameter, nodes and ways reconciled to one shape, and the same rows written as csv and as parquet |
32
+ | [wikidata-country-reference.yaml](wikidata-country-reference.yaml) | SPARQL with content negotiation, the W3C results envelope unwrapped, and a reference table saved for other pipelines to read |
33
+ | [gdacs-disaster-updates.yaml](gdacs-disaster-updates.yaml) | A daily window, deduplication against yesterday's saved list, and what the first day looks like when there is no yesterday |
34
+ | [hdx-dataset-watch.yaml](hdx-dataset-watch.yaml) | The marker pattern: storage standing in for state, and a marker overwritten under `rule: all_done`, whichever way the comparison went |
35
+ | [github-releases-relay.yaml](github-releases-relay.yaml) | A rate limit as a design constraint, string comparison of ISO stamps, and the remaining budget recorded beside the result |
36
+ | [nominatim-geocode-facilities.yaml](nominatim-geocode-facilities.yaml) | One request per second expressed as the shape of the graph: a chain with `time.sleep` between the lookups, not a fan-out |
37
+ | [kobo-submissions-to-csv.yaml](kobo-submissions-to-csv.yaml) | Where a credential belongs, and why a csv needs its columns named up front |
38
+ | [odk-central-submissions.yaml](odk-central-submissions.yaml) | The same survey story with the secret in a connection, and an OData feed with grouped questions |
39
+ | [feeds-composition.yaml](feeds-composition.yaml) | `pipeline.run` over two of the above: parameters down, statuses up, and data across only through storage |
40
+
41
+ ## The two patterns worth stealing
42
+
43
+ **Do nothing, successfully.** There is no `if` in the format and no conditional edge. What there
44
+ is: a decision written to storage as ndjson, and `storage.exists` asked for it with
45
+ `min_size: 1b` and `on_timeout: skip`. An empty decision encodes as an empty file, the floor is
46
+ never met, the sensor skips, and every step behind it skips with it -- while the run still ends
47
+ `succeeded`. `usgs-earthquakes-alert.yaml`, `hdx-dataset-watch.yaml` and
48
+ `github-releases-relay.yaml` each use it, and each says why at the step.
49
+
50
+ **Remember, without a state block.** Nothing carries over between runs except what a run wrote.
51
+ A marker is a small object at a stable path: a sensor asks whether it is there, a transform
52
+ reads it, the comparison decides, and the last step overwrites it under `rule: all_done`.
53
+ `hdx-dataset-watch.yaml` is the whole pattern in one file. Under `--local` the artifact root is
54
+ a throwaway directory, so every local run is a first sighting; on an instance, a stable prefix
55
+ or an `s3://` bucket is what makes the second run the interesting one.
56
+
57
+ ## Two things the reference language will not do
58
+
59
+ A `${...}` reference reads `params`, `steps.<name>.output.<path>`, `item`, and `run` --
60
+ `run.scratch`, `run.id`, `run.window.start`, `run.window.end`. Two consequences show up
61
+ repeatedly on this shelf:
62
+
63
+ - **A parameter default is literal text.** `default: ${run.scratch}/marker.json` is those
64
+ characters, not a path. Anything that has to resolve belongs in a step's config.
65
+ - **A jq program never has a value spliced into it.** Data reaches a program through `input`,
66
+ which is why the earthquake filter carries its thresholds on every row it tests and the
67
+ briefing puts the country in its input rather than in its program.