asx-rs 0.14.0

AS2 and AS4 B2B messaging library for Rust — signing, encryption, MDN, and ebMS3/AS4 profile support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# asx-rs task runner — run `just` to list recipes.
#
# Recipes mirror .github/workflows/rust.yml and the mandatory gates in
# RELEASING.md. When a gate changes there, change it here too (and vice
# versa) so local runs and CI cannot drift apart.

set shell := ["bash", "-euo", "pipefail", "-c"]

# Feature sets kept in sync with the CI `check-test` matrix.
# Mirrors the `check-test` matrix in `.github/workflows/ci.yml`. Keep the two
# in step: a combination only CI builds is one only CI can break.
ci_feature_matrix := "as2,client as4,server as2,as4,client,server,trace as2,as4,testing as2,as4,testing,interop-relaxed as2,as4,compression as4,client,dns"

# Perf gate deliberately omits `testing` — the crate refuses to build release
# artefacts with that feature (it disables WS-Security enforcement gates).
perf_features := "as2,as4,client,server,trace,compression,async-ocsp,interop-strict"

perf_iterations := "2000"
perf_max_regression := "0.25"
perf_baseline := "benches/perf-baseline.txt"

# Uncompiled (```ignore```) doctests. Lower this whenever an example is
# converted; `just doctest-ratchet` says when it can drop.
doctest_ignore_budget := "39"

coverage_threshold := "85"
coverage_files := "src/interop.rs"

fuzz_iterations := "4000"
fuzz_budget_ms := "2500"
fuzz_artifacts := "artifacts/fuzz"

_default:
    @just --list --unsorted

# ---------------------------------------------------------------------------
# Everyday
# ---------------------------------------------------------------------------

# Build with all features.
[group('dev')]
build:
    cargo build --all-features

# Type-check every target with all features.
[group('dev')]
check:
    cargo check --all-targets --all-features

# Type-check with warnings escalated to errors (release gate 2).
[group('dev')]
check-strict:
    RUSTFLAGS='-D warnings' cargo check --all-targets --all-features

# Run the whole test suite. Extra args are forwarded: `just test receipt_verify`.
[group('dev')]
test *args:
    cargo test --all-features {{ args }}

# Unit tests only — fastest useful feedback loop.
[group('dev')]
test-lib *args:
    cargo test --all-features --lib {{ args }}

# Run one integration suite by file stem: `just test-suite as4_push_flow`.
[group('dev')]
test-suite name *args:
    cargo test --all-features --test {{ name }} {{ args }}

# Format the workspace.
[group('dev')]
fmt:
    cargo fmt --all

# Apply rustfmt and every machine-applicable clippy suggestion.
[group('dev')]
fix:
    cargo fmt --all
    cargo clippy --workspace --all-targets --all-features --fix --allow-dirty --allow-staged

# Build rustdoc for this crate only.
[group('dev')]
doc:
    cargo doc --all-features --no-deps

# Build and open rustdoc.
[group('dev')]
doc-open:
    cargo doc --all-features --no-deps --open

# ---------------------------------------------------------------------------
# Lint gates (release gates 1 and 3)
# ---------------------------------------------------------------------------

# Formatting check only — never rewrites files.
[group('lint')]
fmt-check:
    cargo fmt --all -- --check

# Clippy with warnings as errors, exactly as CI runs it.
[group('lint')]
clippy:
    cargo clippy --workspace --all-targets --all-features -- -D warnings

# fmt-check + clippy — the CI `lint` job.
[group('lint')]
lint: fmt-check clippy

# Hygiene scan: no TODO/FIXME/HACK markers, no code-level deprecations.
#
# Deprecation scanning is code-only on purpose: prose in site/ legitimately
# describes deprecated behaviour without introducing any.
[group('lint')]
[doc('Scan for TODO/FIXME/HACK markers and code-level deprecations')]
hygiene:
    #!/usr/bin/env bash
    set -euo pipefail
    if command -v rg >/dev/null 2>&1; then
        scan() { rg -n "$1" src tests site/content .github scripts \
            --glob '!RELEASING.md' || true; }
        scan_code() { rg -n "$1" src tests benches || true; }
    else
        scan() { grep -rnE "$1" src tests site/content .github scripts \
            --exclude=release.md || true; }
        scan_code() { grep -rnE "$1" src tests benches || true; }
    fi
    failed=0
    markers=$(scan 'TODO|FIXME|HACK')
    if [ -n "$markers" ]; then
        echo "hygiene: unresolved work markers found:" >&2
        echo "$markers" >&2
        failed=1
    fi
    deprecations=$(scan_code 'allow\(deprecated\)|#\[deprecated')
    if [ -n "$deprecations" ]; then
        echo "hygiene: code-level deprecation markers found:" >&2
        echo "$deprecations" >&2
        failed=1
    fi
    [ "$failed" -eq 0 ] && echo "hygiene: clean"
    exit "$failed"

# ---------------------------------------------------------------------------
# Test sweeps
# ---------------------------------------------------------------------------

# Lib tests plus each integration suite separately (release gate 4).
#
# Slower than `just test`, but a failure names the suite that broke instead of
# burying it in one combined run.
[group('test')]
[doc('Lib tests plus each integration suite separately (release gate 4)')]
test-sweep:
    #!/usr/bin/env bash
    set -euo pipefail
    cargo test --all-features --lib
    for target in $(find tests -maxdepth 1 -name '*.rs' | sed -E 's@.*/@@; s@\.rs$@@' | sort); do
        echo "── tests/${target}.rs"
        cargo test --all-features --test "$target"
    done

# Compile and test each CI feature combination (release gate 10).
#
# Catches feature-gating breakage that `--all-features` hides — a symbol behind
# the wrong `cfg` compiles fine when everything is on.
[group('test')]
[doc('Compile and test each CI feature combination (release gate 10)')]
matrix:
    #!/usr/bin/env bash
    set -euo pipefail
    for features in {{ ci_feature_matrix }}; do
        echo "── features: ${features}"
        RUSTFLAGS='-D warnings' cargo check --no-default-features --features "$features"
        cargo test --no-default-features --features "$features"
    done

# Compile the benchmarks without running them.
#
# Uses the perf feature set, not `--all-features`: `cargo bench` builds in
# release profile, where the `testing` feature is a hard compile error.
[group('test')]
[doc('Compile the benchmarks without running them')]
bench-check:
    cargo bench --no-default-features --features "{{ perf_features }}" --no-run

# Run the Criterion benchmarks.
[group('perf')]
bench *args:
    cargo bench --no-default-features --features "{{ perf_features }}" {{ args }}

# ---------------------------------------------------------------------------
# Protocol / security gates
# ---------------------------------------------------------------------------

# Adversarial fuzz gate over profile loading, policy resolution and wire parsing.
[group('gate')]
fuzz-gate iterations=fuzz_iterations budget_ms=fuzz_budget_ms out=fuzz_artifacts:
    bash scripts/run_fuzz_gate.sh {{ iterations }} {{ budget_ms }} {{ out }}

# Interop fixture matrix (release gate 6).
[group('gate')]
interop-matrix:
    bash scripts/run_interop_matrix.sh

# WS-Security canonicalization / signature vector gate (release gate 7).
[group('gate')]
wssec-vectors:
    bash scripts/run_wssec_vector_gate.sh

# Lint the profile stack for dead overrides and missing invariants.
[group('gate')]
profile-lint:
    cargo run --quiet -p xtask --all-features -- profile-lint-gate

# Fail on a broken intra-doc link.
#
# These ship to docs.rs as dead references, and rustdoc only reports them as
# warnings — so they accumulate silently.
[group('gate')]
[doc('Fail on broken rustdoc intra-doc links')]
doc-links:
    RUSTDOCFLAGS='-D warnings' cargo doc --all-features --no-deps

# Walk the feature powerset.
#
# A hand-written matrix only covers combinations someone thought of. This is
# the gate that catches a `#[cfg]` whose callee is gated on a different
# feature, and a guard that only applies outside `testing` builds — neither of
# which `--all-features` can see.
#
# Requires cargo-hack: `cargo install cargo-hack`.
[group('gate')]
[doc('Check the feature powerset (depth 2)')]
powerset:
    cargo hack check --feature-powerset --depth 2 \
        --at-least-one-of as2,as4 \
        --exclude-features prometheus,opentelemetry \
        --lib --tests

# Ratchet on uncompiled doctests.
#
# A ```ignore``` block is never compiled, so it renders in rustdoc looking like
# checked Rust while being free to rot. This does not demand zero — it demands
# the number never grows.
[group('gate')]
[doc('Fail if the number of uncompiled (```ignore```) doctests grows')]
doctest-ratchet budget=doctest_ignore_budget:
    bash scripts/check_doctest_ratchet.sh {{ budget }}

# Validate the interop fixture catalogue against the repository.
[group('gate')]
fixtures-validate:
    cargo run --quiet -p xtask --all-features -- fixture-repo-validate

# Line-coverage gate on the profile resolver (release gate 8).
#
# Requires cargo-llvm-cov: `cargo install cargo-llvm-cov`.
[group('gate')]
[doc('Line-coverage gate on the profile resolver (release gate 8)')]
profile-coverage threshold=coverage_threshold files=coverage_files:
    bash scripts/check_profile_coverage.sh {{ threshold }} {{ files }}

# Diff two effective-policy snapshots and classify the risk of the change.
[group('gate')]
profile-diff before after:
    cargo run --quiet -p xtask --all-features -- profile-diff-gate {{ before }} {{ after }}

# ---------------------------------------------------------------------------
# Performance
# ---------------------------------------------------------------------------

# Perf gate against the recorded baseline (release gate 11).
[group('perf')]
perf-gate:
    cargo run --release -p xtask --no-default-features --features "{{ perf_features }}" -- \
        perf-gate --iterations {{ perf_iterations }} \
        --check-baseline {{ perf_baseline }} \
        --max-regression {{ perf_max_regression }}

# Fast perf smoke run, no baseline comparison.
[group('perf')]
perf-smoke iterations="500":
    cargo run --release -p xtask --no-default-features --features "{{ perf_features }}" -- \
        perf-gate --iterations {{ iterations }}

# Rewrite the perf baseline. Only run when a change intentionally moves timings,
# and commit the result with the change that caused it.
[group('perf')]
[doc('Rewrite the perf baseline (only for intentional timing changes)')]
perf-baseline-write:
    cargo run --release -p xtask --no-default-features --features "{{ perf_features }}" -- \
        perf-gate --iterations {{ perf_iterations }} --write-baseline {{ perf_baseline }}

# ---------------------------------------------------------------------------
# Documentation site
# ---------------------------------------------------------------------------

# Build the documentation site. Also fails on broken internal links and
# dangling heading anchors.
[group('site')]
[doc('Build the documentation site (fails on broken links and anchors)')]
site:
    zola --root site build

# Serve the site locally with rewritten URLs.
#
# `zola build` emits absolute URLs from `base_url`, so opening `site/public`
# from disk or a plain file server loads the *deployed* CSS and JS, not the
# local ones. Use this, not a static server, to preview a change.
[group('site')]
[doc('Serve the site locally with rewritten URLs')]
site-serve:
    zola --root site serve

# Regenerate the social card PNG from its SVG source.
#
# Social platforms — X, LinkedIn, Facebook, Slack — do not render SVG previews,
# so `og:image` must point at a raster file. The PNG is committed because the
# deploy job has no SVG rasteriser.
#
# Requires librsvg: `brew install librsvg` / `apt install librsvg2-bin`.
[group('site')]
[doc('Regenerate the social-card PNG from its SVG source (needs librsvg)')]
site-social-card:
    rsvg-convert -w 1200 -h 630 site/static/social-card.svg -o site/static/social-card.png
    @echo "regenerated site/static/social-card.png"

# ---------------------------------------------------------------------------
# Primary sources
# ---------------------------------------------------------------------------

# Download the RFCs, OASIS/W3C standards, network profiles and national
# regulations this crate is written against into specs/ (gitignored;
# specs/README.md is the index and says what each document governs).
#
# Existing files are kept, so a re-run only fills gaps. Anything the publisher
# refuses to a non-browser client is listed at the end for a manual download —
# keep the file name the index uses.
[group('specs')]
[doc('Fetch the primary sources into specs/ (gitignored; see specs/README.md)')]
specs:
    #!/usr/bin/env bash
    set -uo pipefail
    ua='Mozilla/5.0'
    missing=""
    # fetch DIR FILE URL
    fetch() {
        mkdir -p "specs/$1"
        if [ -s "specs/$1/$2" ]; then echo "kept     $1/$2"; return 0; fi
        if curl -fsSL -A "$ua" --retry 3 --retry-delay 5 --max-time 900 \
                -o "specs/$1/$2.part" "$3" && [ -s "specs/$1/$2.part" ]; then
            mv "specs/$1/$2.part" "specs/$1/$2"; echo "fetched  $1/$2"; return 0
        fi
        rm -f "specs/$1/$2.part"
        echo "MISSING  $1/$2  <- $3" >&2
        missing="$missing  $1/$2  <- $3"$'\n'
    }
    # rfc NUMBER SLUG — the IETF text is the normative form; PDFs add nothing.
    rfc() { fetch rfc "rfc$1-$2.txt" "https://www.rfc-editor.org/rfc/rfc$1.txt"; }

    # rfc/ — AS2 and everything AS2 is made of
    rfc 4130 as2-applicability-statement-2
    rfc 3335 ediint-as1-mime-edi
    rfc 5402 as2-compressed-data
    rfc 3274 cms-compressed-data
    rfc 6362 ediint-multiple-attachments
    rfc 6017 ediint-features-header
    rfc 5652 cms
    rfc 5751 smime-3.2-message-specification
    rfc 8551 smime-4.0-message-specification
    rfc 5754 sha2-in-cms
    rfc 4134 smime-example-messages
    rfc 8017 pkcs1-rsa-oaep
    rfc 3394 aes-key-wrap
    rfc 3798 mdn-message-disposition-notification
    rfc 8098 mdn-message-disposition-notification-obsoletes-3798
    rfc 6522 multipart-report
    rfc 2045 mime-1-format
    rfc 2046 mime-2-media-types
    rfc 2047 mime-3-header-extensions
    rfc 2049 mime-5-conformance
    rfc 2387 multipart-related
    rfc 2392 cid-mid-url
    rfc 5322 internet-message-format
    rfc 5280 pkix-certificate-and-crl-profile
    rfc 6960 ocsp
    rfc 5019 ocsp-lightweight-profile
    rfc 9110 http-semantics
    rfc 9112 http-1.1
    rfc 8446 tls-1.3

    # oasis/ — ebMS3 and AS4 themselves
    fetch oasis ebms_core-3.0-spec-os.pdf \
        'https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/core/os/ebms_core-3.0-spec-os.pdf'
    fetch oasis ebms-header-3_0-200704.xsd \
        'https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/core/os/ebms-header-3_0-200704.xsd'
    fetch oasis ebms-v3-part2-advanced-features.pdf \
        'https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/part2/201004/ebms-v3-part2.pdf'
    fetch oasis mf.xsd \
        'https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/part2/201004/mf.xsd'
    fetch oasis AS4-profile-v1.0-os.pdf \
        'https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/profiles/AS4-profile/v1.0/os/AS4-profile-v1.0-os.pdf'
    fetch oasis ebcore-PartyIdType-1.0.pdf \
        'https://docs.oasis-open.org/ebcore/PartyIdType/v1.0/PartyIdType-1.0.pdf'
    fetch oasis bdx-smp-v1.0-os.pdf \
        'https://docs.oasis-open.org/bdxr/bdx-smp/v1.0/os/bdx-smp-v1.0-os.pdf'
    fetch oasis bdx-smp-v2.0-os.pdf \
        'https://docs.oasis-open.org/bdxr/bdx-smp/v2.0/os/bdx-smp-v2.0-os.pdf'
    fetch oasis BDX-Location-v1.0-os.pdf \
        'https://docs.oasis-open.org/bdxr/BDX-Location/v1.0/os/BDX-Location-v1.0-os.pdf'

    # wss/ — WS-Security: the security header AS4 signs and encrypts in
    fetch wss wss-SOAPMessageSecurity-v1.1.1-os.pdf \
        'https://docs.oasis-open.org/wss-m/wss/v1.1.1/os/wss-SOAPMessageSecurity-v1.1.1-os.pdf'
    fetch wss wss-x509TokenProfile-v1.1.1-os.pdf \
        'https://docs.oasis-open.org/wss-m/wss/v1.1.1/os/wss-x509TokenProfile-v1.1.1-os.pdf'
    fetch wss wss-SwAProfile-v1.1.1-os.pdf \
        'https://docs.oasis-open.org/wss-m/wss/v1.1.1/os/wss-SwAProfile-v1.1.1-os.pdf'
    fetch wss oasis-200401-wss-wssecurity-secext-1.0.xsd \
        'https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
    fetch wss oasis-200401-wss-wssecurity-utility-1.0.xsd \
        'https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
    fetch wss oasis-wss-wssecurity-secext-1.1.xsd \
        'https://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd'

    # w3c/ — signature, encryption and the canonicalization both digest through
    fetch w3c xmldsig-core1.html         'https://www.w3.org/TR/xmldsig-core1/'
    fetch w3c xmldsig1-schema.xsd \
        'https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411/xmldsig1-schema.xsd'
    fetch w3c xmldsig-core-schema.xsd \
        'https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd'
    fetch w3c xmldsig-bestpractices.html 'https://www.w3.org/TR/xmldsig-bestpractices/'
    fetch w3c xmlenc-core1.html          'https://www.w3.org/TR/xmlenc-core1/'
    fetch w3c xenc-schema.xsd \
        'https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/xenc-schema.xsd'
    fetch w3c xenc-schema-11.xsd \
        'https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/xenc-schema-11.xsd'
    fetch w3c xml-exc-c14n.html \
        'https://www.w3.org/TR/2002/REC-xml-exc-c14n-20020718/'
    fetch w3c xml-c14n-1.0.html \
        'https://www.w3.org/TR/2001/REC-xml-c14n-20010315'
    fetch w3c xml-c14n11.html            'https://www.w3.org/TR/xml-c14n11/'
    fetch w3c soap-1.1.html              'https://www.w3.org/TR/2000/NOTE-SOAP-20000508/'
    fetch w3c soap12-part1.html          'https://www.w3.org/TR/soap12-part1/'
    fetch w3c soap-envelope-1.2.xsd      'https://www.w3.org/2003/05/soap-envelope/'
    fetch w3c ws-addr-core.html          'https://www.w3.org/TR/ws-addr-core/'
    fetch w3c xop10.html                 'https://www.w3.org/TR/xop10/'
    fetch w3c soap12-mtom.html           'https://www.w3.org/TR/soap12-mtom/'

    # edelivery/ — the CEF profile of AS4 (published as Confluence pages)
    fetch edelivery edelivery-as4-2.0.html \
        'https://ec.europa.eu/digital-building-blocks/sites/spaces/DIGITAL/pages/845480153/eDelivery+AS4+-+2.0'
    fetch edelivery edelivery-as4-1.16.html \
        'https://ec.europa.eu/digital-building-blocks/sites/x/voA4O'
    fetch edelivery edelivery-as4-1.15.html \
        'https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/eDelivery+AS4+-+1.15'
    fetch edelivery edelivery-conformance-testing-as4-user-guide-1.20.pdf \
        'https://ec.europa.eu/digital-building-blocks/sites/download/attachments/467118600/(eDelivery).(Conformance%20Testing%20Service).(User%20Guide).(v1.20).pdf'

    # peppol/ — the largest AS4 network in production
    fetch peppol peppol-as4-profile.html \
        'https://docs.peppol.eu/edelivery/as4/specification/'
    fetch peppol Peppol-EDN-Service-Metadata-Publishing-1.4.0-2025-02-06.pdf \
        'https://docs.peppol.eu/edelivery/smp/Peppol-EDN-Service-Metadata-Publishing-1.4.0-2025-02-06.pdf'
    fetch peppol Peppol-EDN-Service-Metadata-Locator-1.3.0-2025-02-06.pdf \
        'https://docs.peppol.eu/edelivery/sml/Peppol-EDN-Service-Metadata-Locator-1.3.0-2025-02-06.pdf'
    fetch peppol Peppol-EDN-Policy-for-use-of-identifiers-4.4.0-2025-02-06.pdf \
        'https://docs.peppol.eu/edelivery/policies/Peppol-EDN-Policy-for-use-of-identifiers-4.4.0-2025-02-06.pdf'
    fetch peppol PEPPOL-EDN-Policy-for-Transport-Security-1.1.0-2020-04-20.pdf \
        'https://docs.peppol.eu/edelivery/policies/PEPPOL-EDN-Policy-for-Transport-Security-1.1.0-2020-04-20.pdf'
    fetch peppol Peppol-EDN-Business-Message-Envelope-2.0.2-2026-07-02.pdf \
        'https://docs.peppol.eu/edelivery/envelope/Peppol-EDN-Business-Message-Envelope-2.0.2-2026-07-02.pdf'
    fetch peppol Peppol-Network-Policy-1.0.0-2026-07-02.pdf \
        'https://docs.peppol.eu/edelivery/policies/Peppol%20Network%20Policy%201.0.0%202026-07-02.pdf'

    # bdew/ — the German energy market's AS4 profile and the BSI crypto it cites
    fetch bdew bdew-as4-profil-1.2-20260401.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/BK06/BK6_83_Zug_Mess/835_mitteilungen_datenformate/Mitteilung_56/Anlagen/AS4-Profil_1_2_20260401.pdf?__blob=publicationFile&v=1'
    fetch bdew bdew-as4-profil-1.1-v14.pdf \
        'https://bdew-mako.de/pdf/AS4-Profil_1.1_v14.pdf'
    fetch bdew bdew-as4-profil-1.0-20220901.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2021/BK6-21-282/Mitteilung02/AS4%20Profil.pdf?__blob=publicationFile&v=1'
    fetch bdew regelungen-zum-uebertragungsweg-as4-2.4.pdf \
        'https://bdew-mako.de/pdf/Regelungen_zum_%C3%9Cbertragungsweg_AS4_2.4.pdf'
    fetch bdew regelungen-zum-uebertragungsweg-as4-2.2.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/BK06/BK6_83_Zug_Mess/835_mitteilungen_datenformate/Mitteilung_41/Anlagen/Regelungen_zum_%C3%9Cbertragungsweg_AS4_2.2.pdf?__blob=publicationFile&v=1'
    fetch bdew bsi-tr-03116-3.pdf \
        'https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR03116/BSI-TR-03116-3.pdf?__blob=publicationFile&v=4'
    fetch bdew bsi-tr-02102-2-tls.pdf \
        'https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.pdf?__blob=publicationFile&v=4'

    # entsog/ — the European gas market's AS4 usage profile
    fetch entsog entsog-as4-usage-profile-4.0-final.pdf \
        'https://www.entsog.eu/sites/default/files/2025-09/INT2819_25%20AS4%20Usage%20Profile_Version%204.0%20Final.pdf'

    # nist/ — the key-derivation the BDEW ECDH-ES key transport is defined by
    fetch nist NIST.SP.800-56Ar3.pdf \
        'https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar3.pdf'

    # market/ — the implementations this crate is measured against
    fetch market phase4-master.zip \
        'https://github.com/phax/phase4/archive/refs/heads/master.zip'
    fetch market Holodeck-B2B-master.zip \
        'https://github.com/holodeck-b2b/Holodeck-B2B/archive/refs/heads/master.zip'
    fetch market as2-lib-master.zip \
        'https://github.com/phax/as2-lib/archive/refs/heads/master.zip'
    fetch market OpenAs2App-master.zip \
        'https://github.com/OpenAS2/OpenAs2App/archive/refs/heads/master.zip'
    fetch market oxalis-master.zip \
        'https://github.com/OxalisCommunity/oxalis/archive/refs/heads/master.zip'

    if [ -n "$missing" ]; then
        printf '\nNot fetched (download in a browser, keep the file name):\n%s' "$missing" >&2
    fi

# ---------------------------------------------------------------------------
# Composite
# ---------------------------------------------------------------------------

# Pre-push check: everything CI's `lint` and `check-test` jobs run.
[group('composite')]
ci: lint check-strict test matrix powerset

# Every mandatory release gate, in the order RELEASING.md lists them.
#
# Long-running. `just ci` is the fast pre-push subset.
[group('composite')]
[doc('Every mandatory release gate, in RELEASING.md order (slow)')]
release-gate: fmt-check check check-strict clippy doc-links test-sweep powerset interop-matrix wssec-vectors fuzz-gate matrix perf-gate hygiene profile-lint fixtures-validate doctest-ratchet site
    @echo "release-gate: all implemented gates passed"

# Verify the crate packages cleanly for crates.io.
[group('composite')]
publish-dry:
    cargo publish --dry-run --all-features

# Remove build artefacts, including gate output.
[group('composite')]
clean:
    cargo clean
    rm -rf {{ fuzz_artifacts }}