beampipe-palette 0.5.2__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 (40) hide show
  1. beampipe_palette-0.5.2/Containerfile +5 -0
  2. beampipe_palette-0.5.2/HISTORY.md +57 -0
  3. beampipe_palette-0.5.2/LICENSE +11 -0
  4. beampipe_palette-0.5.2/MANIFEST.in +11 -0
  5. beampipe_palette-0.5.2/PKG-INFO +315 -0
  6. beampipe_palette-0.5.2/README.md +277 -0
  7. beampipe_palette-0.5.2/assets/brand/beampipe-terminal-logo.png +0 -0
  8. beampipe_palette-0.5.2/assets/brand/beampipe-terminal-logo.svg +80 -0
  9. beampipe_palette-0.5.2/assets/readme/imagegen-prompts.md +24 -0
  10. beampipe_palette-0.5.2/assets/readme/palette-components-terminal.png +0 -0
  11. beampipe_palette-0.5.2/assets/readme/palette-placement-terminal.png +0 -0
  12. beampipe_palette-0.5.2/beampipe_palette/VERSION +1 -0
  13. beampipe_palette-0.5.2/beampipe_palette/__init__.py +53 -0
  14. beampipe_palette-0.5.2/beampipe_palette/__main__.py +19 -0
  15. beampipe_palette-0.5.2/beampipe_palette/apps.py +161 -0
  16. beampipe_palette-0.5.2/beampipe_palette/component.py +14 -0
  17. beampipe_palette-0.5.2/beampipe_palette/ingest.py +108 -0
  18. beampipe_palette-0.5.2/beampipe_palette/inventory.py +207 -0
  19. beampipe_palette-0.5.2/beampipe_palette/publish.py +1024 -0
  20. beampipe_palette-0.5.2/beampipe_palette.egg-info/PKG-INFO +315 -0
  21. beampipe_palette-0.5.2/beampipe_palette.egg-info/SOURCES.txt +38 -0
  22. beampipe_palette-0.5.2/beampipe_palette.egg-info/dependency_links.txt +1 -0
  23. beampipe_palette-0.5.2/beampipe_palette.egg-info/entry_points.txt +2 -0
  24. beampipe_palette-0.5.2/beampipe_palette.egg-info/requires.txt +22 -0
  25. beampipe_palette-0.5.2/beampipe_palette.egg-info/top_level.txt +1 -0
  26. beampipe_palette-0.5.2/daliuge/palettes/beampipe.palette +402 -0
  27. beampipe_palette-0.5.2/pyproject.toml +15 -0
  28. beampipe_palette-0.5.2/requirements-test.txt +14 -0
  29. beampipe_palette-0.5.2/requirements.txt +4 -0
  30. beampipe_palette-0.5.2/scripts/generate_palette.py +313 -0
  31. beampipe_palette-0.5.2/setup.cfg +4 -0
  32. beampipe_palette-0.5.2/setup.py +57 -0
  33. beampipe_palette-0.5.2/tests/test_apps.py +231 -0
  34. beampipe_palette-0.5.2/tests/test_cli.py +13 -0
  35. beampipe_palette-0.5.2/tests/test_daliuge_pyfunc.py +124 -0
  36. beampipe_palette-0.5.2/tests/test_distribution.py +72 -0
  37. beampipe_palette-0.5.2/tests/test_ingest.py +66 -0
  38. beampipe_palette-0.5.2/tests/test_inventory.py +257 -0
  39. beampipe_palette-0.5.2/tests/test_publish.py +658 -0
  40. beampipe_palette-0.5.2/tests/test_publish_palette.py +141 -0
@@ -0,0 +1,5 @@
1
+ FROM python:3.10-slim
2
+ COPY . /app
3
+ WORKDIR /app
4
+ RUN pip install '.[daliuge]'
5
+ CMD ["python", "-c", "from beampipe_palette import BeampipeIngestApp, BeampipePublishApp; print(BeampipeIngestApp.__name__, BeampipePublishApp.__name__)"]
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ ## 0.5.2 (2026-09-02)
4
+
5
+ - Align native applications and EAGLE metadata with the DALiuGE 6.7 Python
6
+ component guide by making inline component descriptions the palette source.
7
+ - Preserve exact manifest bytes across both DALiuGE 6.6 and 6.7 named pickle
8
+ ports using DALiuGE's serialized-data envelope.
9
+ - Add lazy public exports for both native applications and explicit 6.6/6.7
10
+ runtime qualification lanes.
11
+
12
+ ## 0.5.1 (2026-08-27)
13
+
14
+ - Make GitHub release publication explicit and attach both distribution
15
+ artifacts to the release.
16
+ - Install the wheel build prerequisite in CI and correct the real DALiuGE
17
+ FileDROP flush-order regression.
18
+
19
+ ## 0.5.0 (2026-08-27)
20
+
21
+ - Publish the first public release under the corrected `beampipe-palette`
22
+ distribution and repository name.
23
+ - Rename the Python import namespace to `beampipe_palette`; this is an
24
+ intentional clean break with no misspelled compatibility module.
25
+ - Update generated EAGLE metadata, installed palette paths, CLI packaging,
26
+ documentation, tests, and repository URLs to the corrected spelling.
27
+ - Include the native manifest-ingest and durable output-publisher components,
28
+ callback-free session handoff, filesystem/S3 publication, and the full
29
+ project-neutral offline qualification suite.
30
+
31
+ ## 0.4.0 (development milestone)
32
+
33
+ - Return the verified output-inventory receipt through an atomic,
34
+ execution-scoped session handoff file for remote schedulers.
35
+ - Remove direct Core callbacks and publisher credentials entirely; the control
36
+ plane exclusively owns receipt transport and authentication.
37
+ - Bind every returned receipt to its canonical top-level `execution_id` and
38
+ execution attempt.
39
+
40
+ ## 0.3.0 (development milestone)
41
+
42
+ - Replace the manifest-ingest PyFunc with the native, source-style
43
+ `BeampipeIngestApp` BarrierAppDROP.
44
+ - Reduce ingestion to one Core-configured `manifest_path` setting and one
45
+ pickle-encoded manifest FileDROP output.
46
+ - Generate both native palette components deterministically without the
47
+ `dlg_paletteGen` development dependency.
48
+
49
+ ## 0.2.0 (development milestone)
50
+
51
+ - Replace the experimental publisher PyFunc with a native, two-port
52
+ `BeampipePublishApp` BarrierAppDROP.
53
+ - Remove graph-level runtime/security overrides and the duplicate upstream
54
+ inventory input; retain only project-owned output patterns.
55
+ - Add immutable filesystem and optional S3-compatible publication backends.
56
+ - Add execution-attempt-scoped receipts and runtime credential handling.
57
+ - Add EAGLE palette, offline tests, packaging, and operator documentation.
@@ -0,0 +1,11 @@
1
+ Copyright 2021, The University of Western Australia, ICRAR International Centre for Radio Astronomy Research
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,11 @@
1
+ include LICENSE
2
+ include HISTORY.md
3
+ include Containerfile
4
+ include requirements.txt
5
+ include requirements-test.txt
6
+ graft beampipe_palette
7
+ graft daliuge
8
+ graft assets
9
+ graft scripts
10
+ global-exclude *.py[cod]
11
+ global-exclude __pycache__
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: beampipe-palette
3
+ Version: 0.5.2
4
+ Summary: Project-neutral DALiuGE components for Beampipe
5
+ Home-page: https://github.com/jbwod/beampipe-palette/
6
+ Author: Beampipe contributors
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: wcmatch==11.0
11
+ Provides-Extra: daliuge
12
+ Requires-Dist: daliuge-engine<7,>=6.6; extra == "daliuge"
13
+ Provides-Extra: s3
14
+ Requires-Dist: boto3<2,>=1.38; extra == "s3"
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Requires-Dist: coverage; extra == "test"
18
+ Requires-Dist: flake8; extra == "test"
19
+ Requires-Dist: black; extra == "test"
20
+ Requires-Dist: isort; extra == "test"
21
+ Requires-Dist: pytest-cov; extra == "test"
22
+ Requires-Dist: codecov; extra == "test"
23
+ Requires-Dist: mypy; extra == "test"
24
+ Requires-Dist: gitchangelog; extra == "test"
25
+ Requires-Dist: mkdocs; extra == "test"
26
+ Requires-Dist: build; extra == "test"
27
+ Requires-Dist: wheel; extra == "test"
28
+ Requires-Dist: dlg_paletteGen==0.6.7; extra == "test"
29
+ Dynamic: author
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: home-page
33
+ Dynamic: license-file
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ <p align="center">
40
+ <img src="assets/brand/beampipe-terminal-logo.svg" alt="Beampipe" width="920">
41
+ </p>
42
+
43
+ <p align="center">
44
+ <a href="https://github.com/jbwod/beampipe-palette/actions/workflows/main.yml"><img src="https://github.com/jbwod/beampipe-palette/actions/workflows/main.yml/badge.svg" alt="CI"></a>
45
+ <a href="https://github.com/jbwod/beampipe-palette/releases/tag/v0.5.2"><img src="https://img.shields.io/badge/release-v0.5.2-d6c178?style=flat-square&labelColor=050505" alt="Release v0.5.2"></a>
46
+ <img src="https://img.shields.io/badge/Python-3.10%2B-7fd7e6?style=flat-square&labelColor=050505" alt="Python 3.10 or newer">
47
+ <img src="https://img.shields.io/badge/DALiuGE-6.6%20%7C%206.7-a7cfa3?style=flat-square&labelColor=050505" alt="DALiuGE 6.6 and 6.7 compatible">
48
+ </p>
49
+
50
+
51
+ > `beampipe-palette` provides the two project-neutral native [DALiuGE](https://daliuge.icrar.org/) applications that connect a portable graph to [Beampipe Core](https://github.com/jbwod/beampipe-core-v2): one injects Core's immutable execution manifest at the graph boundary, and one verifies durable outputs at the terminal boundary.
52
+
53
+
54
+ ## `What it does`
55
+
56
+ > - **`Manifest ingress`**: `beampipe-ingest` receives inline JSON injected by Core, canonicalizes it, and emits it once through a path-backed FileDROP.
57
+
58
+ > - **`Durable publication`**: `beampipe-publish` independently discovers every required output, publishes each object create-only, reads it back, and verifies its SHA-256.
59
+
60
+ > - **`Trusted completion evidence`**: the publisher emits `beampipe-output-inventory/v1` to DALiuGE and writes the byte-identical receipt to an attempt-scoped session handoff.
61
+
62
+ > - **`Callback-free remote runs`**: Core retrieves the receipt over its existing SSH/SFTP control connection after Slurm completion. Graphs contain no Core URL, bearer token, storage credential, or project-specific topology.
63
+
64
+
65
+ ## `Native DALiuGE components`
66
+
67
+ <picture>
68
+ <img src="assets/readme/palette-components-terminal.png" alt="Terminal-style beampipe-ingest and beampipe-publish DALiuGE application nodes with their manifest, completion, and inventory ports" />
69
+ </picture>
70
+
71
+ `beampipe-ingest` and `beampipe-publish` are native `BarrierAppDROP`
72
+ applications—not PyFunc snippets. Their graph-facing contracts stay deliberately
73
+ small; Core and the selected deployment profile supply execution details outside
74
+ the logical graph.
75
+
76
+ ### Manifest entering `beampipe-ingest`
77
+
78
+ ```json
79
+ {
80
+ "inputs": {},
81
+ "sources": [{
82
+ "source_identifier": "source-1",
83
+ "groups": [{
84
+ "group_key": "group-1",
85
+ "records": [{
86
+ "record_id": "record-1",
87
+ "access_url": "https://data.example/record-1"
88
+ }]
89
+ }]
90
+ }],
91
+ "_beampipe": {
92
+ "selection": [{
93
+ "source_identifier": "source-1",
94
+ "group_key": "group-1",
95
+ "record_ids": ["record-1"]
96
+ }]
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Inventory emitted by `beampipe-publish`
102
+
103
+ ```json
104
+ {
105
+ "schema": "beampipe-output-inventory/v1",
106
+ "execution_id":
107
+ "607fd31f-db2a-4978-a64f-ef8ce412483a",
108
+ "execution_attempt": 0,
109
+ "patterns": ["**/result.bin"],
110
+ "pattern_counts": {"**/result.bin": 1},
111
+ "products": [{
112
+ "path": "products/result.bin",
113
+ "bytes": 1048576,
114
+ "sha256":
115
+ "a40f3e3d82fd741d64da75530ccb205588b8fbb5a5fbd7d52f18a77ef85d5e60"
116
+ }],
117
+ "inventory_sha256":
118
+ "5efa576ea4393a413c121862a75efcb5086cffa4b651ec77178dfa1c8caa07ce",
119
+ "durable_destination_uri":
120
+ "file:///srv/products/executions/607fd31f-db2a-4978-a64f-ef8ce412483a/attempt-0",
121
+ "publication": {
122
+ "acknowledged": true,
123
+ "publisher": "beampipe-publish",
124
+ "receipt_id":
125
+ "beampipe-publish/v1:5efa576ea4393a413c121862a75efcb5086cffa4b651ec77178dfa1c8caa07ce",
126
+ "published_at": "2026-08-27T10:32:16Z"
127
+ }
128
+ }
129
+ ```
130
+
131
+ The examples show the stable project-neutral fields. Real manifests may add
132
+ project-owned record metadata, while real inventories contain one entry for
133
+ every selected product and the exact hashes observed after durable read-back.
134
+
135
+
136
+ ## `Where the nodes go`
137
+
138
+ <picture>
139
+ <img src="assets/readme/palette-placement-terminal.png" alt="Terminal-style DALiuGE graph flow showing Core manifest into beampipe-ingest, the project graph and completion barrier, then beampipe-publish and the inventory receipt retrieved by Core" />
140
+ </picture>
141
+
142
+ > - Put **`beampipe-ingest` at the graph boundary**. Core patches its
143
+ > `manifest_path` setting with the immutable execution manifest. Connect its
144
+ > single `manifest_bytes` FileDROP to the first project application that needs
145
+ > those bytes.
146
+
147
+ > - Put **`beampipe-publish after the project-owned completion barrier`**. The
148
+ > barrier must become complete only after every required output is closed and
149
+ > stable. Connect the publisher's `inventory` output to one terminal
150
+ > FileDROP.
151
+
152
+ > - Do not draw a callback edge to Core. On remote Slurm deployments the
153
+ > publisher writes the canonical handoff inside the session; Core pulls and
154
+ > verifies it after the scheduler reports completion.
155
+
156
+
157
+ ## `First-time setup`
158
+
159
+ Python 3.10 or newer is required. Install the validated public release into the
160
+ same Python environment used by every DALiuGE execution node:
161
+
162
+ ```bash
163
+ python3 -m venv .venv
164
+ source .venv/bin/activate
165
+ python -m pip install \
166
+ https://github.com/jbwod/beampipe-palette/releases/download/v0.5.2/beampipe_palette-0.5.2-py3-none-any.whl
167
+ beampipe-publish --version
168
+ ```
169
+
170
+ The released wheel is normally installed into an existing DALiuGE runtime. A
171
+ source checkout can create a self-contained DALiuGE 6.x environment with
172
+ `python -m pip install -e '.[daliuge]'`.
173
+
174
+ Install the bounded S3 transport only on runtimes that select an S3
175
+ destination:
176
+
177
+ ```bash
178
+ python -m pip install \
179
+ 'beampipe-palette[s3] @ https://github.com/jbwod/beampipe-palette/archive/refs/tags/v0.5.2.tar.gz'
180
+ ```
181
+
182
+ Download or load the checked-in EAGLE palette:
183
+
184
+ ```text
185
+ https://raw.githubusercontent.com/jbwod/beampipe-palette/v0.5.2/daliuge/palettes/beampipe.palette
186
+ ```
187
+
188
+
189
+ ## `EAGLE graph contract`
190
+
191
+ Load [`daliuge/palettes/beampipe.palette`](daliuge/palettes/beampipe.palette)
192
+ in [EAGLE](https://eagle.icrar.org/). The wheel also installs it at
193
+ `share/beampipe-palette/daliuge/palettes/beampipe.palette` under the active
194
+ Python prefix.
195
+
196
+ | Component | Field | EAGLE usage | Contract |
197
+ |---|---|---|---|
198
+ | `beampipe-ingest` | `manifest_path` | NoPort | inline JSON from Core or an absolute manifest path |
199
+ | `beampipe-ingest` | `manifest_bytes` | OutputPort | one path-backed FileDROP, pickle encoding |
200
+ | `beampipe-publish` | `completion` | InputPort | one project-owned completion FileDROP |
201
+ | `beampipe-publish` | `inventory` | OutputPort | one path-backed canonical inventory FileDROP |
202
+ | `beampipe-publish` | `expected_patterns_json` | NoPort | project-owned JSON string array of required output globs |
203
+
204
+ The Python classes are:
205
+
206
+ ```text
207
+ beampipe_palette.apps.BeampipeIngestApp
208
+ beampipe_palette.apps.BeampipePublishApp
209
+ ```
210
+
211
+ The ingest output intentionally uses DALiuGE's `pickle` encoding. It wraps the
212
+ canonical bytes with DALiuGE's own serialized-data envelope before the single
213
+ pickle port write. This is the common named-port contract that returns exact
214
+ `bytes` on both DALiuGE 6.6 and 6.7. The publisher uses path-backed FileDROPs
215
+ directly and performs no second DROP write.
216
+
217
+ Projects must set `expected_patterns_json` explicitly. Matching is bounded and
218
+ Core-compatible: `*` and `?` stay within one path component, while `**` is
219
+ allowed only as a complete component. Character classes, brace expansion,
220
+ embedded double-stars, traversal, backslashes, controls, and duplicates are
221
+ rejected. The output tree is scanned once regardless of pattern count.
222
+
223
+
224
+ ## `Runtime contract`
225
+
226
+ Runtime details are environment variables, never graph fields:
227
+
228
+ | Environment variable | Meaning |
229
+ |---|---|
230
+ | `BEAMPIPE_OUTPUT_ROOT` | source directory containing completed outputs |
231
+ | `BEAMPIPE_OUTPUT_DESTINATION_URI` | base `file://` or `s3://` destination |
232
+ | `BEAMPIPE_EXECUTION_ID` | canonical execution UUID |
233
+ | `BEAMPIPE_EXECUTION_ATTEMPT` | zero-based locked Core `retry_count` |
234
+ | `BEAMPIPE_OUTPUT_INVENTORY_HANDOFF_PATH` | required attempt-scoped receipt path prepared by the session transport |
235
+
236
+ The durable destination is always namespaced as:
237
+
238
+ ```text
239
+ <base>/executions/<execution_uuid>/attempt-<retry_count>
240
+ ```
241
+
242
+ The handoff path must be absolute, non-root, outside `BEAMPIPE_OUTPUT_ROOT`,
243
+ free of symbolic-link components, and have an existing private parent. No Core
244
+ URL, Core credential, storage secret, project identifier, or output pattern is
245
+ embedded in the component.
246
+
247
+
248
+ ## `Publication guarantees`
249
+
250
+ > - **`Create-only writes`**: files, inventories, and handoffs are never
251
+ > overwritten. An existing byte-identical object is reused; conflicting
252
+ > evidence fails the run.
253
+
254
+ > - **`Read-back verification`**: every durable file or S3 object is retrieved
255
+ > in full and hashed before it is acknowledged.
256
+
257
+ > - **`Exact retries`**: a retry of the same execution attempt reuses the same
258
+ > canonical inventory bytes and publication timestamp. A later attempt has a
259
+ > separate immutable namespace.
260
+
261
+ > - **`Fail-closed evidence`**: missing patterns, empty outputs, symlinks,
262
+ > unsafe paths, oversized receipts, changed files, partial uploads, and
263
+ > mismatched prior evidence all fail publication.
264
+
265
+ Filesystem destinations use absolute, non-overlapping `file://` roots. S3
266
+ destinations require `beampipe-palette[s3]`, boto3 1.38 or newer, conditional
267
+ single-object/multipart creation, and full object read-back. NGAS is not
268
+ claimed until an adapter can provide authoritative file ID/version assignment
269
+ and retrieval verification.
270
+
271
+
272
+ ## `Development`
273
+
274
+ The [DALiuGE 6.7 Python component guide](https://daliuge.readthedocs.io/en/v6.7.0/development/app_development/python_components.html)
275
+ uses `dlg_paletteGen` with inline Doxygen metadata. Install the two system
276
+ tools once on Debian/Ubuntu:
277
+
278
+ ```bash
279
+ sudo apt-get install doxygen xsltproc
280
+ ```
281
+
282
+ ```bash
283
+ make virtualenv
284
+ make lint
285
+ make test
286
+ make palette-check
287
+ make docs
288
+ make build
289
+ ```
290
+
291
+ Palette generation reads the `EAGLE_START` component descriptions in
292
+ `beampipe_palette/apps.py` through pinned `dlg_paletteGen` 0.6.7, then
293
+ canonicalizes IDs and timestamps so the checked EAGLE palette is deterministic.
294
+
295
+ The offline suite covers both native applications, canonical JSON, path and
296
+ port policy, one-pass discovery, filesystem/S3 create-only publication,
297
+ read-back hashes, exact retries, attempt isolation, session handoff, palette
298
+ signature drift, and project-neutral defaults. CI additionally runs the native
299
+ DALiuGE boundary regression against the released 6.6 engine and the pinned 6.7
300
+ source contract used by the linked 6.7 developer documentation.
301
+
302
+
303
+ ## `Documentation`
304
+
305
+ | Task | Page |
306
+ |---|---|
307
+ | Download the current wheel and source archive | [v0.5.2 release](https://github.com/jbwod/beampipe-palette/releases/tag/v0.5.2) |
308
+ | Review the publication sequence | [Publisher guide](docs/index.md) |
309
+ | Import the components into EAGLE | [Checked-in palette](daliuge/palettes/beampipe.palette) |
310
+ | Contribute and run the local gates | [Contributing guide](CONTRIBUTING.md) |
311
+ | Review template provenance | [Template notes](ABOUT_THIS_TEMPLATE.md) |
312
+
313
+ This repository was created from ICRAR's
314
+ [`daliuge-component-template`](https://github.com/ICRAR/daliuge-component-template)
315
+ and retains its [license](LICENSE).
@@ -0,0 +1,277 @@
1
+ <p align="center">
2
+ <img src="assets/brand/beampipe-terminal-logo.svg" alt="Beampipe" width="920">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/jbwod/beampipe-palette/actions/workflows/main.yml"><img src="https://github.com/jbwod/beampipe-palette/actions/workflows/main.yml/badge.svg" alt="CI"></a>
7
+ <a href="https://github.com/jbwod/beampipe-palette/releases/tag/v0.5.2"><img src="https://img.shields.io/badge/release-v0.5.2-d6c178?style=flat-square&labelColor=050505" alt="Release v0.5.2"></a>
8
+ <img src="https://img.shields.io/badge/Python-3.10%2B-7fd7e6?style=flat-square&labelColor=050505" alt="Python 3.10 or newer">
9
+ <img src="https://img.shields.io/badge/DALiuGE-6.6%20%7C%206.7-a7cfa3?style=flat-square&labelColor=050505" alt="DALiuGE 6.6 and 6.7 compatible">
10
+ </p>
11
+
12
+
13
+ > `beampipe-palette` provides the two project-neutral native [DALiuGE](https://daliuge.icrar.org/) applications that connect a portable graph to [Beampipe Core](https://github.com/jbwod/beampipe-core-v2): one injects Core's immutable execution manifest at the graph boundary, and one verifies durable outputs at the terminal boundary.
14
+
15
+
16
+ ## `What it does`
17
+
18
+ > - **`Manifest ingress`**: `beampipe-ingest` receives inline JSON injected by Core, canonicalizes it, and emits it once through a path-backed FileDROP.
19
+
20
+ > - **`Durable publication`**: `beampipe-publish` independently discovers every required output, publishes each object create-only, reads it back, and verifies its SHA-256.
21
+
22
+ > - **`Trusted completion evidence`**: the publisher emits `beampipe-output-inventory/v1` to DALiuGE and writes the byte-identical receipt to an attempt-scoped session handoff.
23
+
24
+ > - **`Callback-free remote runs`**: Core retrieves the receipt over its existing SSH/SFTP control connection after Slurm completion. Graphs contain no Core URL, bearer token, storage credential, or project-specific topology.
25
+
26
+
27
+ ## `Native DALiuGE components`
28
+
29
+ <picture>
30
+ <img src="assets/readme/palette-components-terminal.png" alt="Terminal-style beampipe-ingest and beampipe-publish DALiuGE application nodes with their manifest, completion, and inventory ports" />
31
+ </picture>
32
+
33
+ `beampipe-ingest` and `beampipe-publish` are native `BarrierAppDROP`
34
+ applications—not PyFunc snippets. Their graph-facing contracts stay deliberately
35
+ small; Core and the selected deployment profile supply execution details outside
36
+ the logical graph.
37
+
38
+ ### Manifest entering `beampipe-ingest`
39
+
40
+ ```json
41
+ {
42
+ "inputs": {},
43
+ "sources": [{
44
+ "source_identifier": "source-1",
45
+ "groups": [{
46
+ "group_key": "group-1",
47
+ "records": [{
48
+ "record_id": "record-1",
49
+ "access_url": "https://data.example/record-1"
50
+ }]
51
+ }]
52
+ }],
53
+ "_beampipe": {
54
+ "selection": [{
55
+ "source_identifier": "source-1",
56
+ "group_key": "group-1",
57
+ "record_ids": ["record-1"]
58
+ }]
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Inventory emitted by `beampipe-publish`
64
+
65
+ ```json
66
+ {
67
+ "schema": "beampipe-output-inventory/v1",
68
+ "execution_id":
69
+ "607fd31f-db2a-4978-a64f-ef8ce412483a",
70
+ "execution_attempt": 0,
71
+ "patterns": ["**/result.bin"],
72
+ "pattern_counts": {"**/result.bin": 1},
73
+ "products": [{
74
+ "path": "products/result.bin",
75
+ "bytes": 1048576,
76
+ "sha256":
77
+ "a40f3e3d82fd741d64da75530ccb205588b8fbb5a5fbd7d52f18a77ef85d5e60"
78
+ }],
79
+ "inventory_sha256":
80
+ "5efa576ea4393a413c121862a75efcb5086cffa4b651ec77178dfa1c8caa07ce",
81
+ "durable_destination_uri":
82
+ "file:///srv/products/executions/607fd31f-db2a-4978-a64f-ef8ce412483a/attempt-0",
83
+ "publication": {
84
+ "acknowledged": true,
85
+ "publisher": "beampipe-publish",
86
+ "receipt_id":
87
+ "beampipe-publish/v1:5efa576ea4393a413c121862a75efcb5086cffa4b651ec77178dfa1c8caa07ce",
88
+ "published_at": "2026-08-27T10:32:16Z"
89
+ }
90
+ }
91
+ ```
92
+
93
+ The examples show the stable project-neutral fields. Real manifests may add
94
+ project-owned record metadata, while real inventories contain one entry for
95
+ every selected product and the exact hashes observed after durable read-back.
96
+
97
+
98
+ ## `Where the nodes go`
99
+
100
+ <picture>
101
+ <img src="assets/readme/palette-placement-terminal.png" alt="Terminal-style DALiuGE graph flow showing Core manifest into beampipe-ingest, the project graph and completion barrier, then beampipe-publish and the inventory receipt retrieved by Core" />
102
+ </picture>
103
+
104
+ > - Put **`beampipe-ingest` at the graph boundary**. Core patches its
105
+ > `manifest_path` setting with the immutable execution manifest. Connect its
106
+ > single `manifest_bytes` FileDROP to the first project application that needs
107
+ > those bytes.
108
+
109
+ > - Put **`beampipe-publish after the project-owned completion barrier`**. The
110
+ > barrier must become complete only after every required output is closed and
111
+ > stable. Connect the publisher's `inventory` output to one terminal
112
+ > FileDROP.
113
+
114
+ > - Do not draw a callback edge to Core. On remote Slurm deployments the
115
+ > publisher writes the canonical handoff inside the session; Core pulls and
116
+ > verifies it after the scheduler reports completion.
117
+
118
+
119
+ ## `First-time setup`
120
+
121
+ Python 3.10 or newer is required. Install the validated public release into the
122
+ same Python environment used by every DALiuGE execution node:
123
+
124
+ ```bash
125
+ python3 -m venv .venv
126
+ source .venv/bin/activate
127
+ python -m pip install \
128
+ https://github.com/jbwod/beampipe-palette/releases/download/v0.5.2/beampipe_palette-0.5.2-py3-none-any.whl
129
+ beampipe-publish --version
130
+ ```
131
+
132
+ The released wheel is normally installed into an existing DALiuGE runtime. A
133
+ source checkout can create a self-contained DALiuGE 6.x environment with
134
+ `python -m pip install -e '.[daliuge]'`.
135
+
136
+ Install the bounded S3 transport only on runtimes that select an S3
137
+ destination:
138
+
139
+ ```bash
140
+ python -m pip install \
141
+ 'beampipe-palette[s3] @ https://github.com/jbwod/beampipe-palette/archive/refs/tags/v0.5.2.tar.gz'
142
+ ```
143
+
144
+ Download or load the checked-in EAGLE palette:
145
+
146
+ ```text
147
+ https://raw.githubusercontent.com/jbwod/beampipe-palette/v0.5.2/daliuge/palettes/beampipe.palette
148
+ ```
149
+
150
+
151
+ ## `EAGLE graph contract`
152
+
153
+ Load [`daliuge/palettes/beampipe.palette`](daliuge/palettes/beampipe.palette)
154
+ in [EAGLE](https://eagle.icrar.org/). The wheel also installs it at
155
+ `share/beampipe-palette/daliuge/palettes/beampipe.palette` under the active
156
+ Python prefix.
157
+
158
+ | Component | Field | EAGLE usage | Contract |
159
+ |---|---|---|---|
160
+ | `beampipe-ingest` | `manifest_path` | NoPort | inline JSON from Core or an absolute manifest path |
161
+ | `beampipe-ingest` | `manifest_bytes` | OutputPort | one path-backed FileDROP, pickle encoding |
162
+ | `beampipe-publish` | `completion` | InputPort | one project-owned completion FileDROP |
163
+ | `beampipe-publish` | `inventory` | OutputPort | one path-backed canonical inventory FileDROP |
164
+ | `beampipe-publish` | `expected_patterns_json` | NoPort | project-owned JSON string array of required output globs |
165
+
166
+ The Python classes are:
167
+
168
+ ```text
169
+ beampipe_palette.apps.BeampipeIngestApp
170
+ beampipe_palette.apps.BeampipePublishApp
171
+ ```
172
+
173
+ The ingest output intentionally uses DALiuGE's `pickle` encoding. It wraps the
174
+ canonical bytes with DALiuGE's own serialized-data envelope before the single
175
+ pickle port write. This is the common named-port contract that returns exact
176
+ `bytes` on both DALiuGE 6.6 and 6.7. The publisher uses path-backed FileDROPs
177
+ directly and performs no second DROP write.
178
+
179
+ Projects must set `expected_patterns_json` explicitly. Matching is bounded and
180
+ Core-compatible: `*` and `?` stay within one path component, while `**` is
181
+ allowed only as a complete component. Character classes, brace expansion,
182
+ embedded double-stars, traversal, backslashes, controls, and duplicates are
183
+ rejected. The output tree is scanned once regardless of pattern count.
184
+
185
+
186
+ ## `Runtime contract`
187
+
188
+ Runtime details are environment variables, never graph fields:
189
+
190
+ | Environment variable | Meaning |
191
+ |---|---|
192
+ | `BEAMPIPE_OUTPUT_ROOT` | source directory containing completed outputs |
193
+ | `BEAMPIPE_OUTPUT_DESTINATION_URI` | base `file://` or `s3://` destination |
194
+ | `BEAMPIPE_EXECUTION_ID` | canonical execution UUID |
195
+ | `BEAMPIPE_EXECUTION_ATTEMPT` | zero-based locked Core `retry_count` |
196
+ | `BEAMPIPE_OUTPUT_INVENTORY_HANDOFF_PATH` | required attempt-scoped receipt path prepared by the session transport |
197
+
198
+ The durable destination is always namespaced as:
199
+
200
+ ```text
201
+ <base>/executions/<execution_uuid>/attempt-<retry_count>
202
+ ```
203
+
204
+ The handoff path must be absolute, non-root, outside `BEAMPIPE_OUTPUT_ROOT`,
205
+ free of symbolic-link components, and have an existing private parent. No Core
206
+ URL, Core credential, storage secret, project identifier, or output pattern is
207
+ embedded in the component.
208
+
209
+
210
+ ## `Publication guarantees`
211
+
212
+ > - **`Create-only writes`**: files, inventories, and handoffs are never
213
+ > overwritten. An existing byte-identical object is reused; conflicting
214
+ > evidence fails the run.
215
+
216
+ > - **`Read-back verification`**: every durable file or S3 object is retrieved
217
+ > in full and hashed before it is acknowledged.
218
+
219
+ > - **`Exact retries`**: a retry of the same execution attempt reuses the same
220
+ > canonical inventory bytes and publication timestamp. A later attempt has a
221
+ > separate immutable namespace.
222
+
223
+ > - **`Fail-closed evidence`**: missing patterns, empty outputs, symlinks,
224
+ > unsafe paths, oversized receipts, changed files, partial uploads, and
225
+ > mismatched prior evidence all fail publication.
226
+
227
+ Filesystem destinations use absolute, non-overlapping `file://` roots. S3
228
+ destinations require `beampipe-palette[s3]`, boto3 1.38 or newer, conditional
229
+ single-object/multipart creation, and full object read-back. NGAS is not
230
+ claimed until an adapter can provide authoritative file ID/version assignment
231
+ and retrieval verification.
232
+
233
+
234
+ ## `Development`
235
+
236
+ The [DALiuGE 6.7 Python component guide](https://daliuge.readthedocs.io/en/v6.7.0/development/app_development/python_components.html)
237
+ uses `dlg_paletteGen` with inline Doxygen metadata. Install the two system
238
+ tools once on Debian/Ubuntu:
239
+
240
+ ```bash
241
+ sudo apt-get install doxygen xsltproc
242
+ ```
243
+
244
+ ```bash
245
+ make virtualenv
246
+ make lint
247
+ make test
248
+ make palette-check
249
+ make docs
250
+ make build
251
+ ```
252
+
253
+ Palette generation reads the `EAGLE_START` component descriptions in
254
+ `beampipe_palette/apps.py` through pinned `dlg_paletteGen` 0.6.7, then
255
+ canonicalizes IDs and timestamps so the checked EAGLE palette is deterministic.
256
+
257
+ The offline suite covers both native applications, canonical JSON, path and
258
+ port policy, one-pass discovery, filesystem/S3 create-only publication,
259
+ read-back hashes, exact retries, attempt isolation, session handoff, palette
260
+ signature drift, and project-neutral defaults. CI additionally runs the native
261
+ DALiuGE boundary regression against the released 6.6 engine and the pinned 6.7
262
+ source contract used by the linked 6.7 developer documentation.
263
+
264
+
265
+ ## `Documentation`
266
+
267
+ | Task | Page |
268
+ |---|---|
269
+ | Download the current wheel and source archive | [v0.5.2 release](https://github.com/jbwod/beampipe-palette/releases/tag/v0.5.2) |
270
+ | Review the publication sequence | [Publisher guide](docs/index.md) |
271
+ | Import the components into EAGLE | [Checked-in palette](daliuge/palettes/beampipe.palette) |
272
+ | Contribute and run the local gates | [Contributing guide](CONTRIBUTING.md) |
273
+ | Review template provenance | [Template notes](ABOUT_THIS_TEMPLATE.md) |
274
+
275
+ This repository was created from ICRAR's
276
+ [`daliuge-component-template`](https://github.com/ICRAR/daliuge-component-template)
277
+ and retains its [license](LICENSE).