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,107 @@
1
+ # pipeline.run with wait: true -- a parent step that is a parked row, not a held worker.
2
+ #
3
+ # wait is the default, and it is what makes composition useful: the parent's step finishes
4
+ # when the CHILD finishes, so everything downstream of it genuinely runs after the child's
5
+ # work. The child's final status becomes this step's outcome -- succeeded is succeeded, failed
6
+ # is failed -- and the parent's step output carries the child's code, its run id and its
7
+ # status.
8
+ #
9
+ # HOW IT WAITS MATTERS. pipeline.run is submit-then-probe: the child run is created, a handle
10
+ # is stored, and the parent's attempt is parked with a wake-up time. So a parent waiting on a
11
+ # three-hour child holds a row for three hours and a worker for none of it, and the parent
12
+ # does not have to be running anywhere in particular for the child to progress.
13
+ #
14
+ # poll is the cadence of that probing, and it is a step field like any other. The block's own
15
+ # default is five seconds; 1s below is so the example finishes while you watch it. A real
16
+ # parent waiting on an hour-long child leaves it out.
17
+ #
18
+ # A dirigent document holds exactly one pipeline, so composition is two files, and the apply
19
+ # order matters: the child has to exist before the parent applies at all. requires.pipelines
20
+ # is what says so, and an apply against an instance without the child is refused up front with
21
+ # the code to apply first -- rather than failing at the step, at five in the morning.
22
+ #
23
+ # Hop by hop:
24
+ #
25
+ # load_east runs the child for one region and waits. Its output is the child's identity and
26
+ # outcome, not the child's data: a child's outputs stay in the child's run.
27
+ # archive reads that output back. It runs only because the child finished, which is the
28
+ # guarantee wait: true buys.
29
+ #
30
+ # EXPECT THIS RUN TO SUCCEED in about three seconds, with load_east reporting the child's
31
+ # status as succeeded.
32
+ #
33
+ # Locally there is no instance to have applied the child to, so hand it over:
34
+ #
35
+ # dg run --local examples/patterns/pipeline-run-wait.yaml \
36
+ # --also-apply examples/patterns/pipeline-run-child.yaml
37
+ #
38
+ # Against a server it is two applies and a run:
39
+ #
40
+ # dg apply examples/patterns/pipeline-run-child.yaml
41
+ # dg apply examples/patterns/pipeline-run-wait.yaml
42
+ # dg run pipeline-run-wait --watch
43
+
44
+ format: dirigent/v1
45
+ kind: pipeline
46
+ code: pipeline-run-wait
47
+ name: A parent that waits
48
+ description: |
49
+ `pipeline.run` with the default `wait: true` finishes when the child finishes, and the
50
+ child's status becomes the step's outcome.
51
+
52
+ It is submit-then-probe, so a parent waiting on a three-hour child holds a database row
53
+ and no worker at all.
54
+
55
+ tags: [patterns, pipeline, transform, composition]
56
+
57
+ requires:
58
+ blocks:
59
+ - pipeline.run
60
+ - transform.jq
61
+ # The child, by code. An apply against an instance that does not hold it is refused here
62
+ # rather than at the step.
63
+ pipelines:
64
+ - pipeline-run-child
65
+
66
+ params:
67
+ type: object
68
+ properties:
69
+ region:
70
+ type: string
71
+ description: Which region the child is asked to load.
72
+ default: east
73
+ pattern: "^[a-z][a-z0-9-]*$"
74
+ day:
75
+ type: string
76
+ format: date
77
+ default: "2026-01-01"
78
+
79
+ steps:
80
+ load_east:
81
+ block: pipeline.run
82
+ # Faster than the block's five-second default so the example settles while you watch. A
83
+ # real parent leaves this out: probing a long child every second buys nothing.
84
+ poll: 1s
85
+ config:
86
+ pipeline: pipeline-run-child
87
+ # Written out rather than forwarded. The child's schema is its interface, and a parent
88
+ # that passed its own parameters through would be coupled to whatever it was run with.
89
+ params:
90
+ region: "${params.region}"
91
+ day: "${params.day}"
92
+ # The default, written once so the pair with pipeline-run-fire-and-forget.yaml reads as
93
+ # a choice rather than as an omission.
94
+ wait: true
95
+
96
+ archive:
97
+ block: transform.jq
98
+ depends_on: [load_east]
99
+ config:
100
+ input:
101
+ # The child's identity and outcome. Its data is not here: a child's outputs belong to
102
+ # the child's run, and a parent that needs a value reads it from a store both can see.
103
+ pipeline: "${steps.load_east.output.pipeline}"
104
+ run_id: "${steps.load_east.output.run_id}"
105
+ status: "${steps.load_east.output.status}"
106
+ program: |
107
+ {archived: .pipeline, child_run: .run_id, ended_as: .status}
@@ -0,0 +1,124 @@
1
+ # Passing parameters to a child, and the two things the engine checks about them and when.
2
+ #
3
+ # A parent writes the child's parameters out. It does not forward its own, and there is no
4
+ # syntax for "pass everything through" -- because the child's schema is the contract, and a
5
+ # parent that forwarded whatever it was run with would break the moment either side grew a
6
+ # parameter.
7
+ #
8
+ # WHEN THE CHECKING HAPPENS, in two stages:
9
+ #
10
+ # at apply requires.pipelines is checked, so a parent naming a child the instance does
11
+ # not hold is refused with the code to apply first.
12
+ # at execute the params map below is validated against the CHILD's schema, by the child, as
13
+ # the step runs. So a wrong parameter is a failed step with the schema's own
14
+ # message, not a refused apply -- the parent has no way to know at apply time
15
+ # what a ${params.x} will resolve to.
16
+ #
17
+ # max_depth is the other guard, and it counts the chain reaching this run rather than the
18
+ # calls this document makes. The default is 5, so a parent calling a child that calls a child
19
+ # is fine and a cycle is not: a pipeline that eventually calls itself fails at the depth limit
20
+ # instead of filling the instance with runs.
21
+ #
22
+ # A FAN-OUT OF pipeline.run IS THE USEFUL SHAPE. One item per region, each starting its own
23
+ # child run with its own parameters, all of them parked rows rather than held workers. That is
24
+ # how a parent runs eleven children in parallel without eleven of anything.
25
+ #
26
+ # Hop by hop:
27
+ #
28
+ # plan builds the list of regions to load, so the fan-out's width is one value the
29
+ # rest of the document reads.
30
+ # load one child run per region, waited on, each with its own parameters. Note the
31
+ # day is the same for all of them and the region is not: an element carries what
32
+ # differs, and params carries what does not.
33
+ # summarise the join. It reads the batch of child outcomes -- codes, run ids and statuses,
34
+ # which is everything a parent gets back from a child.
35
+ #
36
+ # EXPECT THIS RUN TO SUCCEED in about five seconds, with three child runs and three statuses
37
+ # of succeeded.
38
+ #
39
+ # To change it: -p regions='["east"]' runs one child; a region that does not match the child's
40
+ # pattern fails that item at execute time with the child's own message, which is the two-stage
41
+ # checking above made visible.
42
+ #
43
+ # dg run --local examples/patterns/pipeline-run-with-params.yaml \
44
+ # --also-apply examples/patterns/pipeline-run-child.yaml
45
+ # dg run --local examples/patterns/pipeline-run-with-params.yaml \
46
+ # --also-apply examples/patterns/pipeline-run-child.yaml -p regions='["east","west"]'
47
+
48
+ format: dirigent/v1
49
+ kind: pipeline
50
+ code: pipeline-run-with-params
51
+ name: Children, one per region
52
+ description: |
53
+ A parent writes its child's parameters out explicitly; they are validated against the
54
+ **child's** schema when the step executes, not when the parent is applied.
55
+
56
+ A fan-out of `pipeline.run` is how one parent runs many children in parallel, each a
57
+ parked row rather than a held worker.
58
+
59
+ tags: [patterns, pipeline, transform, composition, fan-out]
60
+
61
+ requires:
62
+ blocks:
63
+ - pipeline.run
64
+ - transform.jq
65
+ pipelines:
66
+ - pipeline-run-child
67
+
68
+ params:
69
+ type: object
70
+ properties:
71
+ regions:
72
+ type: array
73
+ description: One child run per element.
74
+ default: [east, west, north]
75
+ minItems: 1
76
+ maxItems: 16
77
+ items:
78
+ type: string
79
+ # The same pattern the child declares. Duplicating it here is what turns a child-side
80
+ # failure at execute time into a parent-side refusal before the run exists.
81
+ pattern: "^[a-z][a-z0-9-]*$"
82
+ day:
83
+ type: string
84
+ format: date
85
+ description: The day every child loads; the same for all of them.
86
+ default: "2026-01-01"
87
+
88
+ steps:
89
+ plan:
90
+ block: transform.jq
91
+ config:
92
+ input: "${params.regions}"
93
+ program: |
94
+ {regions: ., count: length}
95
+
96
+ load:
97
+ block: pipeline.run
98
+ depends_on: [plan]
99
+ # The fan-out reads params, not the plan step's output: cardinality is fixed when the run
100
+ # is created, so a for_each names params, run, or another fan-out's grid -- never a step's
101
+ # output.
102
+ for_each: "${params.regions}"
103
+ # One region refusing is not a reason to abandon the others; the join below counts what
104
+ # actually landed.
105
+ items: continue
106
+ poll: 1s
107
+ config:
108
+ pipeline: pipeline-run-child
109
+ params:
110
+ # What differs per child comes from the element.
111
+ region: "${item}"
112
+ # What is the same for every child comes from the parent's parameters.
113
+ day: "${params.day}"
114
+
115
+ summarise:
116
+ block: transform.jq
117
+ depends_on: [load]
118
+ config:
119
+ input: "${steps.load.output}"
120
+ program: |
121
+ {
122
+ children: [.[] | {pipeline, run_id, status}],
123
+ started: length
124
+ }
@@ -0,0 +1,102 @@
1
+ # poll: what the cadence buys, and what it costs, measured on the same wait twice.
2
+ #
3
+ # poll is how often a sensor looks. It is not how long the wait is, and it is not accuracy for
4
+ # free: a sensor learns nothing between pokes, so the answer arrives up to one whole poll
5
+ # interval late. Coarse polling is cheap and blunt; fine polling is precise and chatty. That
6
+ # is the entire trade, and this file measures it rather than asserting it.
7
+ #
8
+ # Two time.sleep sensors, both asked for exactly the same wait, differing only in cadence.
9
+ # time.sleep reports waited_ms, which is the configured duration plus however much of a poll
10
+ # interval it had to sit through after the wait was already over -- so the overshoot is
11
+ # readable in the output instead of having to be timed by hand.
12
+ #
13
+ # NOTE WHERE THE CADENCE IS WRITTEN. poll, deadline, timeout and retry are step fields, and
14
+ # the reference language reaches config and for_each only. So the two cadences below are
15
+ # literals: a run's shape is not something a caller talks the engine into at run time. Only
16
+ # the wait itself, which is block config, takes a parameter.
17
+ #
18
+ # Hop by hop:
19
+ #
20
+ # fine poll: 1s over the wait. It pokes once a second, and waited_ms lands close to the
21
+ # configured duration.
22
+ # coarse poll: 4s over the same wait. The wait being over is not noticed until the next
23
+ # poke, so waited_ms lands up to four seconds higher.
24
+ # compare reads both and subtracts. The difference is what the coarse cadence cost in
25
+ # latency, and it is bounded by the poll interval, which is the rule of thumb.
26
+ #
27
+ # The two sensors have no edge between them, so they wait side by side and the run takes about
28
+ # as long as the slower one rather than the sum. Width in a DAG is not a keyword: it is what
29
+ # two steps that do not name each other already are.
30
+ #
31
+ # EXPECT THIS RUN TO SUCCEED, in about eight to nine seconds, with a coarse_cost_ms of
32
+ # somewhere between zero and four seconds depending on where the wait fell between pokes.
33
+ #
34
+ # To change it: -p wait=20 makes the run longer without changing the gap, because the
35
+ # overshoot depends on the cadence and not on the length of the wait -- which is exactly the
36
+ # thing worth internalising before choosing a poll interval for a twelve-hour sensor.
37
+ #
38
+ # dg run --local examples/patterns/poll-cadence.yaml
39
+ # dg run --local examples/patterns/poll-cadence.yaml -p wait=20
40
+
41
+ format: dirigent/v1
42
+ kind: pipeline
43
+ code: poll-cadence
44
+ name: What a poll interval costs
45
+ description: |
46
+ The same wait polled every second and every four seconds, with the difference in
47
+ `waited_ms` reported.
48
+
49
+ A sensor learns nothing between pokes, so its answer is late by up to one poll interval.
50
+ Cheap and blunt, or precise and chatty -- measured here rather than asserted.
51
+
52
+ tags: [patterns, sensor, transform, timeout]
53
+
54
+ requires:
55
+ blocks:
56
+ - time.sleep
57
+ - transform.jq
58
+
59
+ params:
60
+ type: object
61
+ properties:
62
+ wait:
63
+ type: integer
64
+ description: How many seconds both sensors are asked to wait for.
65
+ default: 6
66
+ minimum: 1
67
+ maximum: 60
68
+
69
+ steps:
70
+ fine:
71
+ block: time.sleep
72
+ # A literal, because a step field is not interpolated. One second is a chatty cadence for
73
+ # anything real; it is here so the tight end of the trade is visible.
74
+ poll: 1s
75
+ config:
76
+ # Config is interpolated, and a configured duration is written humanely rather than as
77
+ # a bare number of seconds, so the parameter goes inside a string that ends in s.
78
+ for: "${params.wait}s"
79
+
80
+ coarse:
81
+ block: time.sleep
82
+ # The same wait, looked at a quarter as often. Nothing else differs, which is what makes
83
+ # the two outputs comparable.
84
+ poll: 4s
85
+ config:
86
+ for: "${params.wait}s"
87
+
88
+ compare:
89
+ block: transform.jq
90
+ depends_on: [fine, coarse]
91
+ # A join: no rule written, so all_success, and it waits for both sensors.
92
+ config:
93
+ input:
94
+ fine_ms: "${steps.fine.output.waited_ms}"
95
+ coarse_ms: "${steps.coarse.output.waited_ms}"
96
+ program: |
97
+ {
98
+ fine_ms,
99
+ coarse_ms,
100
+ coarse_cost_ms: (.coarse_ms - .fine_ms),
101
+ rule_of_thumb: "a sensor's answer is late by up to one poll interval"
102
+ }
@@ -0,0 +1,120 @@
1
+ # One pipeline, three answers to "how urgently", each overriding the one before it.
2
+ #
3
+ # Every run carries a priority -- low, normal, or high -- and the claim takes a higher one
4
+ # first. It is one word, and it LAYERS the way parameters already do:
5
+ #
6
+ # the document priority: low what this pipeline is by default
7
+ # a trigger priority: normal what a schedule or webhook fires it at
8
+ # an ad hoc run --priority high what a person asks for, once
9
+ #
10
+ # The specific one wins, and the answer is PINNED ON THE RUN when it is created. Editing this
11
+ # document tomorrow does not reorder a run that started today, which is the same promise the
12
+ # pinned pipeline version makes about everything else a run executes.
13
+ #
14
+ # WHAT THE THREE WORDS ARE FOR, in the case this document is drawn from -- a bulk reload that
15
+ # takes hours and matters to nobody in particular:
16
+ #
17
+ # low its default. It is enormous, it is not urgent, and it should be behind
18
+ # whatever a person is waiting on. That is what low means: last.
19
+ # normal what the nightly schedule fires it at, because a nightly run that never
20
+ # finishes is a nightly run that failed. Ordinary, not deferred.
21
+ # high what an incident asks for by hand. The reload is now the thing being waited
22
+ # on, so it goes ahead of everything queued.
23
+ #
24
+ # THE CLAIM'S ORDER IS PRIORITY, THEN FAIRNESS, THEN DUE TIME. Fairness is round-robin
25
+ # between runs: the claim ranks each run's due attempts within that run and takes one from
26
+ # each in turn, so this document's fan-out over four regions interleaves with a two-step run
27
+ # queued beside it rather than holding every worker slot until it drains. Priority sorts
28
+ # ahead of that, so a high run's attempts still come first.
29
+ #
30
+ # WHAT PRIORITY CANNOT DO: it never takes a slot that is already busy. An attempt that is
31
+ # running is never cancelled to make room for an urgent one, because that means killing work
32
+ # with side effects nobody can take back. A high run is claimed first the moment a slot frees
33
+ # -- so with every slot held by a long step, it still waits for one to finish.
34
+ #
35
+ # Hop by hop:
36
+ #
37
+ # plan turns the parameters into the regions this reload covers.
38
+ # reload a fan-out, one item per region -- the many small units that make a run worth
39
+ # being fair about in the first place.
40
+ # receipt joins them back and reports what was covered.
41
+ #
42
+ # EXPECT THIS RUN TO SUCCEED in about a second, with no network. A local run is alone in a
43
+ # throwaway instance, so --priority is refused there: there is nothing to be ahead of.
44
+ #
45
+ # dg run --local examples/patterns/priority-layered.yaml
46
+ # dg apply examples/patterns/priority-layered.yaml
47
+ # dg run priority-layered # low: the document's own
48
+ # dg run priority-layered --priority high # the incident case
49
+ # dg runs list # ! marks high, a muted low marks low
50
+
51
+ format: dirigent/v1
52
+ kind: pipeline
53
+ code: priority-layered
54
+ name: Low by default, high by hand
55
+ description: |
56
+ `priority` is one word -- `low`, `normal` or `high` -- and it layers: the document declares
57
+ the default, a schedule or webhook overrides it for what it triggers, and `dg run
58
+ --priority` overrides it once more. The resolved word is pinned on the run at creation.
59
+
60
+ The claim orders by priority, then round-robin fairness between runs, then due time.
61
+ Nothing is preempted: an attempt already running is never cancelled for an urgent one.
62
+
63
+ tags: [patterns, transform, priority, schedule]
64
+
65
+ # Low, because a bulk reload should be behind whatever somebody is waiting on.
66
+ priority: low
67
+
68
+ requires:
69
+ blocks:
70
+ - transform.jq
71
+
72
+ params:
73
+ type: object
74
+ properties:
75
+ regions:
76
+ type: array
77
+ description: The regions this reload covers; one fan-out item each.
78
+ items:
79
+ type: string
80
+ default: [nordics, nepal, sahel, andes]
81
+
82
+ steps:
83
+ plan:
84
+ block: transform.jq
85
+ config:
86
+ input:
87
+ regions: "${params.regions}"
88
+ program: |
89
+ {regions, total: (.regions | length)}
90
+
91
+ reload:
92
+ block: transform.jq
93
+ depends_on: [plan]
94
+ # A fan-out is where fairness earns its keep: four items here, four hundred in the real
95
+ # thing, and every one of them is a unit the claim interleaves with other runs' work.
96
+ for_each: "${params.regions}"
97
+ config:
98
+ input:
99
+ region: "${item}"
100
+ program: |
101
+ {region, rows: 1200}
102
+
103
+ receipt:
104
+ block: transform.jq
105
+ depends_on: [reload]
106
+ config:
107
+ input: "${steps.reload.output}"
108
+ program: |
109
+ {regions: length, rows: (map(.value.rows) | add)}
110
+
111
+ triggers:
112
+ schedules:
113
+ - code: nightly-reload
114
+ name: Nightly, at ordinary urgency
115
+ description: A nightly run that never finishes is a nightly run that failed.
116
+ cron: "0 2 * * *"
117
+ timezone: UTC
118
+ # The schedule overrides the document's low for what IT fires, and nothing else. An
119
+ # ad hoc run of the same pipeline is still low unless it says otherwise.
120
+ priority: normal
@@ -0,0 +1,186 @@
1
+ # Every ${...} form the reference language allows, each used once, in one document.
2
+ #
3
+ # THERE ARE FOUR NAMESPACES AND NOTHING ELSE. No expressions, no arithmetic, no conditionals,
4
+ # no functions, no defaults, no coalescing. A reference names a value or it does not resolve.
5
+ #
6
+ # params.<name> a run parameter
7
+ # params.<name>.<path> a leaf inside an object parameter
8
+ # params.<name>.<index> an element of an array parameter
9
+ # steps.<key>.output a step's whole output
10
+ # steps.<key>.output.<field> one field of it
11
+ # steps.<key>.output.<index> one element of a fan-out step's output list
12
+ # steps.<key>.items the grid a fan-out maps over, and only for_each reads it
13
+ # steps.<key>.item.output the matching item's output, in a step that shares its grid
14
+ # item this run item's element, inside a step that fans out
15
+ # item.<path> a field of it, when the element is an object
16
+ # run.id this run's uuid
17
+ # run.scratch this run's own directory under the artifact root
18
+ # run.window.start the start of the interval this run covers
19
+ # run.window.end the end of it, exclusive
20
+ #
21
+ # TWO RULES ABOUT WHAT A REFERENCE BECOMES:
22
+ #
23
+ # A reference that is the WHOLE value resolves to the typed value. "${params.count}" is the
24
+ # integer 4 downstream, "${params.window}" is an object, "${steps.x.output}" is an array.
25
+ # A reference INSIDE a larger string interpolates as text: "day-${params.day}" is a string,
26
+ # null renders as empty, and a boolean renders as true or false.
27
+ #
28
+ # AND ONE ABOUT WHAT DOES NOT RESOLVE. An unknown reference is not an empty string: it raises,
29
+ # and the engine settles that attempt as rejected, naming what was actually available. That
30
+ # applies to a misspelled parameter, a step that produced no output, a field that is not
31
+ # there, ${item} outside a fan-out, and ${run.window.start} on a run that carries no window.
32
+ #
33
+ # WHERE REFERENCES ARE RESOLVED: in a step's config, and in for_each. Nowhere else. poll,
34
+ # deadline, timeout, retry, rule and concurrency are the pipeline's shape, and a caller does
35
+ # not get to talk the engine into a different one at run time.
36
+ #
37
+ # One more rule this document cannot show without the allowlist: in a config field a block
38
+ # marked as a shell string -- shell.run's `command` -- every substituted value is shell-quoted,
39
+ # so a parameter that arrived in a webhook payload becomes exactly one word and the
40
+ # metacharacters the author typed keep their meaning.
41
+ #
42
+ # THIS FILE NEEDS A WINDOW. run.window.* is a property of the run, so an ad hoc run has to be
43
+ # given one; without it this document is refused at the first step, naming the reference. That
44
+ # refusal is the demonstration too:
45
+ #
46
+ # dg run --local examples/patterns/references-cheat-sheet.yaml --window 2026-06-01..2026-06-02
47
+ # dg run --local examples/patterns/references-cheat-sheet.yaml # refused: this run carries no window
48
+
49
+ format: dirigent/v1
50
+ kind: pipeline
51
+ code: references-cheat-sheet
52
+ name: The whole reference language
53
+ description: |
54
+ Four namespaces -- `params`, `steps`, `item`, `run` -- and no expressions, conditionals or
55
+ functions anywhere.
56
+
57
+ A whole reference resolves to the typed value; one inside a larger string interpolates as
58
+ text; an unknown one is rejected rather than resolved to empty. Needs `--window`.
59
+
60
+ tags: [patterns, transform, fan-out, references]
61
+
62
+ requires:
63
+ blocks:
64
+ - transform.jq
65
+
66
+ params:
67
+ type: object
68
+ properties:
69
+ day:
70
+ type: string
71
+ format: date
72
+ default: "2026-06-01"
73
+ count:
74
+ type: integer
75
+ description: An integer, so the typed-value rule has something to be visible on.
76
+ default: 4
77
+ minimum: 1
78
+ regions:
79
+ type: array
80
+ description: An array, indexed once below and fanned out over once.
81
+ default: [east, west, north]
82
+ minItems: 1
83
+ items:
84
+ type: string
85
+ window:
86
+ type: object
87
+ description: An object parameter, so a nested leaf has somewhere to be.
88
+ default: {days: 7, align: midnight}
89
+ properties:
90
+ days: {type: integer}
91
+ align: {type: string}
92
+
93
+ steps:
94
+ namespaces:
95
+ block: transform.jq
96
+ config:
97
+ input:
98
+ # params, three ways: whole, a nested leaf, and an element by index.
99
+ day: "${params.day}"
100
+ align: "${params.window.align}"
101
+ first_region: "${params.regions.0}"
102
+ # An integer stays an integer, because the reference is the entire value.
103
+ typed_count: "${params.count}"
104
+ # The same reference inside a larger string is text, and reads as text downstream.
105
+ interpolated: "loading ${params.count} regions for ${params.day}"
106
+ # run: the two values every run has.
107
+ run_id: "${run.id}"
108
+ scratch: "${run.scratch}"
109
+ # run.window: only on a run that carries one. Half-open, so start is included and
110
+ # end is not.
111
+ window_start: "${run.window.start}"
112
+ window_end: "${run.window.end}"
113
+ program: |
114
+ .
115
+
116
+ per_region:
117
+ block: transform.jq
118
+ # for_each is the one place outside config where references resolve.
119
+ for_each: "${params.regions}"
120
+ depends_on: [namespaces]
121
+ config:
122
+ input:
123
+ # item: this run item's element. Outside a fan-out this reference does not resolve.
124
+ region: "${item}"
125
+ # A step's whole output, and one field of it, from inside a fan-out.
126
+ day: "${steps.namespaces.output.value.day}"
127
+ program: |
128
+ {region, day}
129
+
130
+ paired_region:
131
+ block: transform.jq
132
+ depends_on: [per_region]
133
+ # steps.<key>.items is the grid per_region maps over, so this step gets that grid rather
134
+ # than one of its own and the two are paired by item position.
135
+ for_each: "${steps.per_region.items}"
136
+ config:
137
+ input:
138
+ # ...which is what lets this read the MATCHING item's output instead of the whole
139
+ # list. fan-out-item-wise.yaml is this on its own.
140
+ region: "${steps.per_region.item.output.value.region}"
141
+ # ${item} is the same element it was in per_region.
142
+ element: "${item}"
143
+ program: |
144
+ {region, element}
145
+
146
+ per_feed:
147
+ block: transform.jq
148
+ # A literal list of objects, so item.<path> has somewhere to reach.
149
+ for_each:
150
+ - {code: cases, weight: 2}
151
+ - {code: climate, weight: 1}
152
+ depends_on: [namespaces]
153
+ config:
154
+ input:
155
+ # item.<path>: a field of an element that is an object. A field the element does not
156
+ # carry raises here rather than resolving to an empty string.
157
+ feed: "${item.code}"
158
+ weight: "${item.weight}"
159
+ # ${item} on its own is still the whole element.
160
+ element: "${item}"
161
+ program: |
162
+ {feed, weight, element}
163
+
164
+ collect:
165
+ block: transform.jq
166
+ depends_on: [per_region, paired_region, per_feed, namespaces]
167
+ config:
168
+ input:
169
+ # A fan-out step's whole output: a JSON array of the items' outputs, in item order.
170
+ every_item: "${steps.per_region.output}"
171
+ # One element of it, by index. Under items: continue a failed item is absent from the
172
+ # list, so index 0 is the first item that WORKED rather than the first one asked for.
173
+ first_item: "${steps.per_region.output.0.value.region}"
174
+ paired: "${steps.paired_region.output}"
175
+ feeds: "${steps.per_feed.output}"
176
+ # A whole step output, unindexed, so the object arrives intact.
177
+ namespaces: "${steps.namespaces.output.value}"
178
+ program: |
179
+ {
180
+ regions: [.every_item[].value.region],
181
+ paired: [.paired[].value.region],
182
+ feeds: [.feeds[].value.feed],
183
+ first_item,
184
+ covered: "\(.namespaces.window_start)..\(.namespaces.window_end)",
185
+ typed_count_is_a_number: (.namespaces.typed_count | type)
186
+ }