Repository navigation
935 lines (829 loc) · 41.1 KB
/
Copy pathci.yml
File metadata and controls
935 lines (829 loc) · 41.1 KB
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
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
name: CI
on:
push:
# Release branches are included so the tag-time `ci-gate` job can find a
# successful run at the tag SHA. Without them, cutting a release from a
# `v*` branch either blocks the release workflows or ships unverified.
branches: [main, "release/**", "v[0-9]+.[0-9]+.[0-9]+"]
pull_request:
# T34: without this, the only way to mint a fresh CI run for a SHA (to
# satisfy the release-time ci-gate after e.g. a transient failure) was to
# re-run an old run or push an empty commit — which changes the SHA.
# Note ci-gate filters on event=push, so a dispatch run satisfies a re-run
# need but not the gate itself; use "Re-run all jobs" on the push run for
# that.
workflow_dispatch:
schedule:
# Weekly full sweep (Sundays 05:23 UTC, off the on-the-hour herd). Two
# checks are too slow to earn a slot on every push but must not go
# unrun: `msrv` builds AND tests on 1.98 here rather than only checking,
# and `package` verify-builds the crates.io tarballs regardless of what
# changed. Everything else simply runs again, which also catches
# dependency drift that no commit of ours would have triggered.
- cron: "23 5 * * 0"
permissions:
contents: read
# One group per PR: superseded pushes cancel the in-flight run — around
# twenty jobs including a full test suite, a Docker build, and a seven-leg
# container matrix.
# Branch pushes (main, release/**, v*) group by COMMIT instead, so no two
# pushes ever share a group and every pushed SHA runs to completion. This
# cannot be done by queueing on a shared ref group: GitHub keeps at most
# one running plus one pending run per group and CANCELS an older pending
# run even with cancel-in-progress false — which would leave a SHA with no
# completed run for the release-time ci-gate lookup to find.
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
jobs:
# Path gate for the jobs too slow to run unconditionally. GitHub has no
# per-job path filter (that is why cross-os-build.yml is a separate
# workflow), so the filtering is done here and consumed via `needs`.
changes:
name: Changed paths
runs-on: ubuntu-22.04
permissions:
contents: read
outputs:
packaging: ${{ steps.filter.outputs.packaging }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The diff is taken against the previous commit / the merge base,
# neither of which a shallow clone contains.
fetch-depth: 0
# `packaging` answers: could this change have broken a crates.io
# tarball? Two ways it can, and only two.
#
# 1. A manifest moved — the `include` allowlist itself, a dependency,
# or the version.
# 2. A file was ADDED or RENAMED under crates/. That is the failure
# T29 describes: `include` is an allowlist, so a newly added file
# that the build needs but the list does not name drops silently
# out of the package and only surfaces mid-release.
#
# Editing an existing, already-shipped file cannot drop it out of the
# tarball, which is why plain modifications under src/ do not qualify —
# and they are the overwhelming majority of pushes. Anything unexpected
# (a first push, a force-push, a scheduled or dispatched run) falls
# through to `true`: this gate may only ever skip work it is certain
# about.
- name: Filter
id: filter
env:
EVENT: ${{ github.event_name }}
BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
HEAD: ${{ github.sha }}
run: |
set -euo pipefail
packaging=true
if [ "$EVENT" != "schedule" ] && [ "$EVENT" != "workflow_dispatch" ] \
&& [ -n "${BASE:-}" ] \
&& [ "$BASE" != "0000000000000000000000000000000000000000" ] \
&& git cat-file -e "${BASE}^{commit}" 2>/dev/null; then
changed=$(git diff --name-status "$BASE" "$HEAD")
manifests=$(printf '%s\n' "$changed" | awk '{print $NF}' \
| grep -E '^(Cargo\.(toml|lock)|crates/[^/]+/Cargo\.toml|\.github/workflows/ci\.yml)$' || true)
newfiles=$(printf '%s\n' "$changed" \
| grep -E '^(A|R[0-9]*)[[:space:]]+crates/' || true)
if [ -z "$manifests" ] && [ -z "$newfiles" ]; then
packaging=false
fi
fi
echo "packaging=$packaging" >> "$GITHUB_OUTPUT"
echo "packaging=$packaging"
fmt:
name: Format
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
components: rustfmt
- name: Check formatting
run: cargo fmt --all --check
lint:
name: Lint
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
components: clippy
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# docs.rs renders this crate whether or not the library is a supported
# embedding API, and a broken intra-doc link ships as broken there. The
# lints are warn-by-default and nothing was reading them, so 18 had
# accumulated by 1.0.
- name: Rustdoc
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --lib
# rust-version in Cargo.toml is a public stability promise; this job is
# the only thing that keeps it honest (proposal T9).
msrv:
name: MSRV (1.98)
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "1.98"
# Its own entry, not `linux-stable`: a different rustc hashes every
# artifact differently, so there is nothing to share.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
# The promise is that a contributor on 1.98 can build AND test, so the
# weekly sweep still runs the whole suite. Per push it only *checks* —
# `--all-targets` so dev-dependencies stay honest about the MSRV, and
# `--locked` so a resolver bump that needs a newer rustc is caught.
#
# What actually regresses an MSRV is a language feature or an API that
# 1.98 does not have, and `cargo check` catches all of it; running the
# tests again on a second toolchain re-ran a suite `test` had already
# run green, for 8 minutes a push. Codegen- and runtime-level breaks are
# what the weekly full run is for.
- name: Check on MSRV
if: github.event_name != 'schedule'
run: cargo check --workspace --all-targets --locked
- name: Test on MSRV (weekly sweep)
if: github.event_name == 'schedule'
run: cargo test --workspace --no-fail-fast
examples:
name: Examples
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Build
run: cargo build
- name: Lint example workflows
run: |
# workflow*.json, not workflow.json: channel-composition ships a
# second workflow (workflow-lookup.json) the narrower glob skipped.
#
# --definitions: a workflow written in source form resolves its
# `$from`, `$use` and `$each` against the package's own shared
# document, which a per-file lint does not otherwise load.
for f in examples/packages/*/workflow*.json; do
./target/debug/orion-server lint "$f" --definitions "$(dirname "$f")"
done
# `--plugin-dir`: the fixed-width-statement cases run the example's
# codec for real, so its manifest and component are handed to the run.
- name: Run example workflow tests (offline, stubbed connectors, real plugin)
run: ./target/debug/orion-server test examples/workflow-tests --plugin-dir examples/packages/fixed-width-statement --plugin-dir examples/packages/c4-tournament --model-dir examples/packages/c4-tournament/entrant
- name: Start Postgres (postgres-orders package)
run: docker compose -f examples/packages/postgres-orders/docker-compose.yml up --wait postgres
# Plugins on: the fixed-width-statement package uploads one.
- name: Start Orion
run: |
ORION_STORAGE__URL="sqlite:/tmp/orion-examples.db?mode=rwc" \
ORDERS_DB_URL="postgres://orion:orion@localhost:5432/orion_orders" \
ORION_PLUGINS__ENABLED=true \
./target/debug/orion-server > /tmp/orion-examples.log 2>&1 &
for i in $(seq 1 30); do
curl -fsS http://localhost:8080/healthz > /dev/null 2>&1 && exit 0
sleep 1
done
echo "server did not become healthy" >&2
cat /tmp/orion-examples.log >&2
exit 1
- name: Quickstart script (twice — idempotency)
run: |
./examples/quickstart.sh
./examples/quickstart.sh
- name: Deploy every example package end-to-end (twice — idempotency)
run: |
cd examples
for d in packages/*/; do
[ -f "$d/workflow.json" ] || continue
./deploy.sh "${d%/}"
./deploy.sh "${d%/}"
done
- name: Server log on failure
if: failure()
run: cat /tmp/orion-examples.log || true
# The plugin SDK, proven rather than claimed: the test fixture and the
# example codec are rebuilt from their Rust sources on the shipped
# `orion-plugin-sdk`, and the suites that pin the ABI run against the fresh
# components rather than the committed bytes. A byte-for-byte diff against
# the committed files would break on every rustc release; behavioural
# equivalence is the contract. Path-filtered by the `changes` job's
# plugin-surface output would be tidier, but the job is a minute on top of
# a warm cache, so it runs with the rest.
plugin-sdk:
name: Plugin SDK
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Install wasm-tools
run: cargo install wasm-tools --locked
- name: Rebuild the test fixture from source on the SDK
run: crates/orion-server/tests/fixtures/plugins/build.sh
- name: Rebuild the example codec from source on the SDK
run: examples/plugins/fixed-width/build.sh
# The c4-tournament referee, the second example guest. Its committed
# component is what the offline cases below run, so rebuilding it here
# is what makes "against the fresh codec" true of both of them.
- name: Rebuild the example referee from source on the SDK
run: examples/plugins/c4-rules/build.sh
# The fixture was rebuilt in place, so the committed-bytes path and the
# fresh path are the same file; naming it explicitly keeps the suite's
# override path exercised.
- name: Run the ABI cases and the sandbox suites against the fresh fixture
env:
ORION_PLUGIN_FIXTURE: crates/orion-server/tests/fixtures/plugins/fixture.wasm
run: cargo test -p orion-server --test integration -- plugin_abi_cases_test plugin_runtime_test plugin_sdk_test
- name: Run the examples' offline cases against the fresh components
run: cargo run -p orion-server -- test examples/workflow-tests --plugin-dir examples/packages/fixed-width-statement --plugin-dir examples/packages/c4-tournament --model-dir examples/packages/c4-tournament/entrant
- name: The SDK's own tests
run: cargo test -p orion-plugin-sdk
# The full default suite on its own job (proposal T8): unit and integration
# signal must not ride the coverage job, whose hand-rolled swapfile and
# OOM exposure can wipe all of it at once. Container-gated tests stay in
# integration-containers.
test:
name: Test
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# The single writer of the `linux-stable` cache.
#
# Ten per-job entries at ~0.6-1.3GB each used to sit against GitHub's
# 10GB repository limit — measured at 10.24GB over 80 entries on
# 2026-08-30 — so every push evicted the entries it had just written.
# The macOS, Windows and musl caches were gone from the store entirely,
# and jobs here restored in 0-1s (a miss) and then recompiled the whole
# dependency graph cold.
#
# `cargo test --workspace --all-targets` builds a superset of what
# `lint`, `examples`, `cli-e2e`, `package`, `mutants` and
# `integration-containers` each need, so it owns the write and they
# restore it read-only. One writer keeps the save deterministic: with
# several, whichever finished first won the key and the rest logged a
# 409.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
# --no-fail-fast: without it cargo stops after the first test binary that
# fails, so one broken unit test suppresses every integration result
# and a red CI is diagnosed one round-trip at a time.
- name: Run tests
run: cargo test --workspace --all-targets --no-fail-fast
# --all-targets silently skips doctests; the config structs carry
# heavily documented examples that would otherwise never compile.
- name: Run doctests
run: cargo test --doc
# The workspace shell e2e suite (tests/e2e/) drives a real orion-server
# over HTTP with the orion-cli binary — the one place the two crates' API
# contract is exercised end to end at the same commit. Before the monorepo
# merge this needed an out-of-band server binary and could not run per PR
# at all.
cli-e2e:
name: CLI E2E (orion-cli against orion-server)
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Build both binaries
run: cargo build -p orion-server -p orion-cli
# jq and curl are preinstalled on the GitHub ubuntu images.
- name: Run the e2e suite
run: E2E_SKIP_BUILD=1 ./tests/e2e/run.sh
# Advisories AND license compatibility / bans / source policy over the
# whole tree (proposal T12) — deny.toml is the policy, including the one
# documented advisory ignore. cargo-deny reads manifests and the lockfile
# and compiles nothing, so it carries no rust-cache: its entry was 124MB of
# pure eviction pressure on a shared 10GB budget for no build to warm.
deny:
name: Dependency Check (cargo-deny)
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
- name: Install cargo-deny
uses: taiki-e/install-action@9983c65e42da123ff25d1f78505eb6de315aa172 # v2 (tool from `with`)
with:
tool: cargo-deny
- name: Run cargo-deny
run: cargo deny check
# T29: crates-publish.yml runs `cargo publish --locked` on the release tag —
# the first time anything exercises the crates.io tarball. Cargo.toml uses
# an `include` allowlist, so a newly added required file silently drops out
# of the package and would only be discovered mid-release. `cargo package`
# assembles the tarball and verify-builds it from its own contents on every
# PR instead.
package:
name: Package (crates.io tarball)
runs-on: ubuntu-22.04
needs: changes
# 6.7 minutes (404s, measured 2026-08-29) on every push, to verify-build
# four tarballs that only a manifest edit or a newly added file can break.
# See the `changes` job for why those two are the whole list.
if: needs.changes.outputs.packaging == 'true'
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Assemble and verify-build the publish tarballs
run: cargo package --locked --workspace
# The docs site is deployed by Cloudflare Workers Builds on merges touching
# docs/**, entirely outside GitHub, so without this a PR
# can break the book (dangling SUMMARY entry, bad include) and nobody
# notices until after merge. book.toml sets create-missing = false, so a
# missing SUMMARY target fails the build here instead of fabricating an
# empty page (proposal T25).
book:
name: Book (mdbook)
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install mdbook
uses: taiki-e/install-action@9983c65e42da123ff25d1f78505eb6de315aa172 # v2
with:
tool: mdbook@0.5.4
# The same script Cloudflare Workers Builds runs to deploy, so a PR also
# exercises the llms-full.txt generation and the _redirects/_headers
# staging, not just `mdbook build`. mdbook is already on PATH from the
# step above, so the script's pinned-binary fetch is skipped here —
# mdbook_pin_drift_test is what keeps that pin and this one equal.
- name: Build book
run: bash docs/build.sh
# The docs estate's structural guard: include/link integrity, redirect
# coverage for hard site links, the one-owner-per-fact guard greps, and
# (check 21) conformance to DOCUMENTATION_STANDARD.md — page types, the
# template sections each type must carry, callout vocabulary, sentence
# length, hub size and navigation depth.
- name: Docs lint
run: bash docs/lint.sh
# The prose half of DOCUMENTATION_STANDARD.md §12. Vale carries the
# Google developer documentation style guide plus docs/styles/Orion —
# the words §6.7 bans, the link text §10.6 requires, and the vocabulary
# §6.4 holds constant. `vale sync` fetches the Google package, which is
# not vendored: it is a third-party style with its own release cadence,
# and pinning a copy here would mean maintaining it.
#
# Failing, per §12's "start advisory and tighten to failing over one
# quarter": the flag came off when docs/lint.sh's DOCS_STRUCTURE_LEVEL
# became `enforce`, and the two switch together.
# vale is a Go binary published to GitHub Releases, and it is not one of
# the tools `taiki-e/install-action` manages. Asking that action for it
# falls through to cargo-binstall, which resolves the name on crates.io
# — where `vale` is an unrelated 0.0.0 crate of the same name — so the
# step could only ever fail with "no version matching requirement". It
# never ran: the `Docs lint` step above it was failing first, and the
# install-action bump in #322 is what surfaced it.
#
# Taken from the release directly, against the checksum the release
# publishes. That pins the bytes the way the SHA-pinned actions above
# are pinned; the cost is that a version bump here needs its checksum
# updated with it, which the two `VALE_` values keep in one place.
- name: Install Vale
env:
VALE_VERSION: 3.21.0
VALE_SHA256: 96997d19a4ca6981673b0d4c5ca7f3ede4a9f97964a96edc29fba9e28a328336
run: |
set -euo pipefail
archive="vale_${VALE_VERSION}_Linux_64-bit.tar.gz"
curl -sSfL -o "$archive" \
"https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/${archive}"
echo "${VALE_SHA256} ${archive}" | sha256sum -c -
tar -xzf "$archive" vale
sudo install -m 0755 vale /usr/local/bin/vale
rm -f "$archive" vale
vale --version
- name: Docs prose
working-directory: docs
run: |
vale sync
vale --output=line src
# The chart's guards — two `{{- fail }}`s, two `required`s and
# values.schema.json — only fire at render time, so before this job a typo
# shipped undetected until a user's `helm install` (proposal T13). Lint and
# template need a values file: the chart's defaults deliberately `required`-
# fail without storage.url/redisUrl, so each documented install shape is
# rendered explicitly — devStack, production, and production with the
# Prometheus Operator ServiceMonitor. `helm template` also renders
# `templates/tests/`, which nothing else in CI touches. kubeconform then
# type-checks the output against the Kubernetes schemas (CRDs-catalog
# supplies the ServiceMonitor type).
helm:
name: Helm Chart
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install kubeconform
run: |
curl -sSfL -o /tmp/kubeconform.tar.gz \
https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-linux-amd64.tar.gz
tar -xzf /tmp/kubeconform.tar.gz -C /tmp kubeconform
sudo install /tmp/kubeconform /usr/local/bin/kubeconform
- name: Lint and render each value set
run: |
set -euo pipefail
for values in deploy/helm/orion/ci/*-values.yaml; do
echo "=== ${values}"
helm lint deploy/helm/orion -f "${values}" --strict
helm template orion deploy/helm/orion -f "${values}" \
| kubeconform -strict -summary \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
done
# The schema must keep refusing what the templates do not read — that
# is the bug it exists to prevent (see values.schema.json in
# proposal.md's "already strong" list).
- name: A misspelled value must fail the render
run: |
if helm template orion deploy/helm/orion \
-f deploy/helm/orion/ci/production-values.yaml \
--set cluster.enabld=true > /dev/null 2>&1; then
echo "::error::values.schema.json accepted 'cluster.enabld' — the typo guard is gone"
exit 1
fi
# The image build breaks independently of cargo (system deps like the
# rdkafka toolchain, base-image drift). Building on every PR keeps that
# from being discovered mid-release (proposal T10). No push — the release
# workflow owns publishing.
#
# What an ordinary push needs from this job is proof that the image
# *builds* — system deps, the chef stages, the runtime stage — and that
# does not need the slowest codegen settings Rust offers. It was 14.3
# minutes (855s, measured 2026-08-29) building the `dist` profile: thin LTO
# on top of codegen-units = 1. On push and PR it now relaxes both knobs via
# the ARG block in the Dockerfile.
#
# NOTHING THAT SHIPS IS AFFECTED. codegen-units = 1 is deliberate — it is
# what stops the binary carrying the same function up to 25 times over —
# and it stays the default in [profile.release], in [profile.dist], and in
# the Dockerfile's own ARG defaults. docker-release.yml passes no override,
# so published images build exactly as before, and cargo-dist reads
# [profile.dist] straight from Cargo.toml. This override reaches one image:
# the `push: false` one built here and thrown away.
#
# The weekly sweep still builds the true `dist` shape, so a break that only
# appears under thin LTO or single-codegen-unit — a duplicate symbol at
# link time, say — cannot hide until a release. It builds into the release
# cache scope, since at those settings its layers are exactly what a
# release build wants, which also warms that scope weekly rather than
# never. Push builds use `linux/amd64-ci` because layers cooked under a
# different profile are unusable to a release build and would only poison
# it. ha-drill.yml reads the `-ci` scope and passes the same overrides so
# it keeps hitting the cache.
docker-build:
name: Docker Build
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Build image (no push)
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
platforms: linux/amd64
push: false
# GIT_HASH is the build-arg the release build passes too
# (.dockerignore excludes .git/, so build.rs cannot ask git). The
# profile args below are this job's own — see the note above the
# job for why, and for why nothing that ships sees them.
build-args: |
GIT_HASH=${{ github.sha }}
CARGO_PROFILE_DIST_CODEGEN_UNITS=${{ github.event_name == 'schedule' && '1' || '16' }}
CARGO_PROFILE_DIST_LTO=${{ github.event_name == 'schedule' && 'thin' || 'false' }}
# mode=max, not min: min exports only the final image's layers, and
# in this multi-stage build every expensive layer (`cargo chef
# cook`, the app build) lives in the builder stage that the runtime
# stage merely COPYs the binary out of. Under mode=min this job
# would cache nothing and rebuild cold every push.
cache-from: type=gha,scope=${{ github.event_name == 'schedule' && 'linux/amd64' || 'linux/amd64-ci' }}
cache-to: type=gha,scope=${{ github.event_name == 'schedule' && 'linux/amd64' || 'linux/amd64-ci' }},mode=max
coverage:
name: Test Coverage
runs-on: ubuntu-22.04
# T42: this job carried `continue-on-error: true` while it proved it
# could survive the runner (T8's disk-freeing + swapfile era). Its last
# 15 completed runs are green (checked 2026-08-01), which is the
# promotion criterion the old comment set — the `--fail-under-lines`
# ratchet below is now a real floor, not a report.
permissions:
contents: read
steps:
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
components: llvm-tools-preview
# Its own entry, not `linux-stable`: instrumented builds carry different
# RUSTFLAGS, so these artifacts are not interchangeable with the ones
# `test` writes.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Add swap space
run: |
sudo swapoff /swapfile || true
sudo rm -f /swapfile
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@9983c65e42da123ff25d1f78505eb6de315aa172 # v2 (tool from `with`)
with:
tool: cargo-llvm-cov
# --fail-under-lines turns the artifact into a ratchet: measured at
# 89.47% line / 89.97% function on the 1.0 tree, floored a little below
# that so an ordinary refactor has room to move but a module landing
# with no tests does not.
#
# Raise the floor when the real number moves up; never lower it to make
# a red build green. Blocking since T42 (the job earned it — see the
# note on `coverage:` above).
# Scoped to the default members (orion-server): orion-cli is neither
# compiled nor instrumented here, so this percentage describes the
# server only. TESTING.md's layer-6 note says the same.
- name: Generate coverage report
run: cargo llvm-cov --lcov --output-path lcov.info -j 2 --fail-under-lines 88
- name: Upload coverage artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
path: lcov.info
# Coverage answers "did this line run", which is not the same question as
# "would anything have failed if it were wrong". Four of the error-code
# strings in errors.rs sat at full line coverage and could still be renamed
# with the whole suite green, because every assertion around them checked
# the HTTP status instead.
#
# --in-diff keeps this proportional: only code the PR actually touched is
# mutated, intersected with the security- and correctness-critical globs in
# .cargo/mutants.toml. A PR touching none of them generates no mutants and
# passes in seconds.
mutants:
name: Mutation Testing (changed code)
# The one job off the repo-wide ubuntu-22.04 pin. cargo-mutants ships
# prebuilt binaries linked against glibc 2.39 from v27.1.0 (2026-06-02)
# on, and 22.04 carries glibc 2.35 — the install step died with
# "cargo-mutants: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.39'
# not found". That pin exists so shipped binaries stay loadable on older
# glibc; this job ships nothing, so it does not apply here. Bumping the
# runner rather than pinning an older cargo-mutants keeps the tool on
# `latest` like every other install-action call in this file.
runs-on: ubuntu-24.04
# PR-only: --in-diff needs two commits to compare, and on a branch push
# there is no natural base to diff against.
if: github.event_name == 'pull_request'
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full history: the diff is taken against the merge base, which a
# shallow clone does not contain.
fetch-depth: 0
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: Install cargo-mutants
uses: taiki-e/install-action@9983c65e42da123ff25d1f78505eb6de315aa172 # v2 (tool from `with`)
with:
tool: cargo-mutants
- name: Diff against the merge base
run: git diff "origin/${{ github.base_ref }}"... > /tmp/pr.diff
# Surviving mutants fail this step. To accept one as genuinely
# equivalent, annotate the function `#[mutants::skip]` with a comment
# saying why — that records the judgement in the code rather than
# widening a glob nobody revisits.
- name: Mutate the changed code
run: cargo mutants --in-diff /tmp/pr.diff --no-shuffle -j 2
- name: Upload mutants report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mutants-report
path: mutants.out/
# The container suites, one job each (they were six sequential steps in one
# job: 462s + 258s + 69s + 87s + 75s + 285s = 20.6 minutes, the longest pole
# in CI by a factor of two). They were already independent —
# the old steps carried `if: !cancelled()` precisely so a flaky Kafka broker
# could not hide a Postgres regression — so a matrix with `fail-fast: false`
# keeps exactly that property and pays the wall clock of the slowest leg
# instead of their sum.
#
# `run:` is the matrix key on purpose, not `cmd:` or `args:`. The drift
# guards in ci_filter_drift_test parse this file as text: they union the
# name filters found on every container invocation line, and
# just_test_containers_matches_the_ci_invocation compares the first such
# line against the justfile after stripping a `run:` prefix. Keeping the key
# named `run` means each command still reads as the same `run:` line it was,
# and the guards needed no change. Keep `data-sql` first and each command on
# one line, and mirror any edit in `just test-containers`.
#
# Careful when editing the prose here: a comment line that contains both
# the binary selector and the ignored flag is picked up as if it were an
# invocation, which is why this paragraph names neither together.
integration-containers:
name: Containers (${{ matrix.name }})
runs-on: ubuntu-22.04
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# GitHub-hosted ubuntu runners ship Docker, so testcontainers spins
# up ephemeral Postgres / MySQL / MongoDB / Elasticsearch / Redis /
# Kafka containers on demand. Filters are substring matches on the
# full module::test path — every module with #[ignore] tests must
# appear on one of these lines by module name, or its tests never
# run anywhere.
#
# The first two legs are one suite split in half because it was the
# critical path: 462s as one leg,
# against 258s for the next longest. Divided by the backend each
# module starts a container for, which is what the time is actually
# spent on — Postgres/MySQL on one, MongoDB/Elasticsearch/Redis/
# Vault on the other. Every test brings up its own container, so the
# grouping is free to change; keep the two legs balanced.
- name: data-sql
run: cargo test --test integration -- --ignored postgres_test mysql_test db_column_types_test data_roundtrip_test data_parity_test integrity_errors_test sql_check_test
- name: data-nosql
run: cargo test --test integration -- --ignored mongodb_test es_test connector_redis_test dynamic_inputs_test vault_test
# Kafka gets its own leg: each test starts its own broker container,
# and sharing one invocation with the Postgres/MySQL/Mongo/ES
# containers starves them — producers fail with MessageTimedOut
# while brokers fight for CPU. Measured on the pre-existing suite
# (no changes): 4 Kafka failures in the combined run, 13/13 green
# when Kafka runs on its own. Scheduling pressure, not a defect.
# On its own runner now, which can only help; if it is still flaky,
# add `--test-threads=2`.
- name: kafka
run: cargo test --test integration -- --ignored kafka_test
# Orion's own storage on Postgres / MySQL. Separate binaries (one
# process each) because the storage backend is pinned per process
# via the DB_BACKEND OnceLock.
- name: storage-postgres
run: cargo test --test storage_postgres -- --ignored
- name: storage-mysql
run: cargo test --test storage_mysql -- --ignored
# D10: migrate all three backends from scratch and assert the
# schemas agree — columns and their nullability, normalised types,
# and every idx_* index with its ordered column list. Only the
# SQLite half runs without Docker, so on a machine with no daemon
# this leg is the only place the three are compared.
- name: schema-parity
run: cargo test --test schema_parity -- --ignored
# Multi-node cluster-mode tests: two AppStates over one Postgres +
# one Redis container per test. Serial — each test runs its own
# containers.
- name: cluster
run: cargo test --test cluster -- --ignored --test-threads=1
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master (toolchain from `with`)
with:
toolchain: "stable"
# Restores the shared `linux-stable` entry written by `test`; never
# writes, so one cache serves every stable-Linux job instead of ten.
# This matters more here than anywhere: seven legs restoring one warm
# entry is what makes fanning out cost roughly the same runner minutes
# as the single job did.
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
with:
shared-key: linux-stable
save-if: false
- name: Install system dependencies
uses: ./.github/actions/apt-build-deps
- name: ${{ matrix.name }}
run: ${{ matrix.run }}