Repository navigation
983 lines (968 loc) · 54.1 KB
/
Copy pathrust.yml
File metadata and controls
983 lines (968 loc) · 54.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
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
name: Rust
on:
workflow_dispatch:
inputs:
production:
description: Also verify the fixed vhalla.com public site
type: boolean
required: false
default: false
legacy_browser:
description: Also build and exercise the retained browser app (not a release artifact)
type: boolean
required: false
default: false
push:
branches: [main]
pull_request:
workflow_call:
inputs:
legacy_browser:
description: Also build and exercise the retained browser app (not a release artifact)
type: boolean
required: false
default: false
outputs:
browser_manifest_sha256:
description: Manifest commitment when legacy browser verification was requested
value: ${{ jobs.browser-artifact.outputs.manifest_sha256 }}
browser_artifact_id:
description: Immutable artifact selected by the successful browser producer
value: ${{ jobs.browser-artifact.outputs.artifact_id }}
browser_artifact_digest:
description: SHA-256 of the successful browser producer's artifact ZIP
value: ${{ jobs.browser-artifact.outputs.artifact_digest }}
permissions:
contents: read
# Pull requests cancel their superseded runs. Main, dispatch and release runs
# group by commit so a later merge never cancels an earlier commit's gate.
concurrency:
group: rust-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Default-to-run change filter. A pull request skips the Rust and
# platform gates only when EVERY changed path is on the non-Rust allowlist
# below; any other path (including new top-level directories and vectors/)
# runs all native gates. Pushes to main, dispatches, the nightly and release
# calls run every native gate. Browser app qualification is opt-in through
# legacy_browser on dispatch or workflow_call; it is not a headless release
# dependency. Retained browser/shared crates still receive workspace checks.
#
# Within that, .github/scripts/verify_scope.py narrows the four expensive
# verification groups to the packages a change can reach: prototype-checks
# (one entry per touched prototype crate and every prototype that depends on
# a touched crate), kani-spent, the four browser journeys, and formal. It
# scopes pull requests and pushes to main only, and fails closed: a missing
# or incomplete change list, a CI/selector/Cargo/vendor/vectors change, an
# unmapped path or any selector error runs every group in full. The nightly
# (nightly.yml), dispatch and release (release.yml) select every native group.
# An opted-in browser run still uses the browser reachability result.
changes:
name: Change filter
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
rust: ${{ steps.filter.outputs.rust }}
formal: ${{ steps.filter.outputs.formal }}
prototypes: ${{ steps.filter.outputs.prototypes }}
kani: ${{ steps.filter.outputs.kani }}
browser: ${{ steps.legacy-browser.outputs.selected }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- id: filter
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
REF: ${{ github.ref }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
CHANGED_FILES: ${{ github.event.pull_request.changed_files }}
BEFORE: ${{ github.event.before }}
run: |
set -euo pipefail
rust=true
changed="$RUNNER_TEMP/changed-paths.txt"
complete=false
if [ "$EVENT" = pull_request ]; then
rows="$(gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[] | [.filename, (.previous_filename // "")] | @tsv')"
count="$(printf '%s\n' "$rows" | sed '/^$/d' | wc -l | tr -d ' ')"
paths="$(printf '%s\n' "$rows" | tr '\t' '\n' | sed '/^$/d')"
nonrust='^((site|docs|kb)/.+|vercel\.json|\.vercelignore|LICENSE|railway\.toml|[^/]+\.md)$'
# The files API lists at most 3,000 paths; anything incomplete runs everything.
# Here-strings, not `printf | grep -q`: under pipefail an early
# grep -q match SIGPIPEs printf on lists over the 64 KiB pipe
# buffer, and the `!` would turn that into a false skip.
if [ "$count" -gt 0 ] && [ "$count" = "$CHANGED_FILES" ]; then
printf '%s\n' "$paths" > "$changed"
complete=true
if ! grep -Evq "$nonrust" <<<"$paths" \
&& ! grep -qx 'CHANGELOG.md' <<<"$paths" \
&& ! grep -qx 'site/install.ps1' <<<"$paths" \
&& ! grep -qx 'site/tools/browser-contract.mjs' <<<"$paths"; then
rust=false
fi
fi
printf 'Changed paths (%s):\n%s\n' "$count" "$paths"
elif [ "$EVENT" = push ] && [ "$REF" = refs/heads/main ]; then
# Diff the pushed range. An unknown or unreachable base (new
# branch, force push) leaves the list incomplete: run everything.
if [[ "$BEFORE" =~ ^[0-9a-f]{40}$ ]] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ] \
&& git fetch --no-tags --depth=1 origin "$BEFORE" \
&& git diff --name-only --no-renames "$BEFORE" "$GITHUB_SHA" > "$changed"; then
complete=true
printf 'Changed paths since %s:\n' "$BEFORE"
cat "$changed"
fi
fi
args=(--event "$EVENT" --ref "$REF" --rust "$rust" --output "$RUNNER_TEMP/scope.txt")
if [ "$complete" = true ]; then
args+=(--changed-files "$changed")
fi
if ! python3 .github/scripts/verify_scope.py "${args[@]}"; then
# The selector itself failed: run every group.
echo "::warning::verification selector failed; running every group"
manifests="$(find prototypes -mindepth 2 -maxdepth 2 -name Cargo.toml | sort | jq -R . | jq -cs .)"
printf 'scope=full\nprototypes=%s\nkani=true\nbrowser=true\nformal=true\n' "$manifests" > "$RUNNER_TEMP/scope.txt"
if [ "$rust" = false ]; then
printf 'prototypes=[]\nkani=false\nbrowser=false\n' >> "$RUNNER_TEMP/scope.txt"
fi
fi
printf 'rust=%s\n' "$rust" | tee -a "$GITHUB_OUTPUT"
# Later duplicate keys win, so the fallback above can override.
tee -a "$GITHUB_OUTPUT" < "$RUNNER_TEMP/scope.txt"
{
echo '### Verification scope'
echo
sed 's/^/- /' "$RUNNER_TEMP/scope.txt"
} >> "$GITHUB_STEP_SUMMARY"
- name: Select legacy browser verification
id: legacy-browser
env:
LEGACY_BROWSER: ${{ inputs.legacy_browser }}
RUST_CHANGED: ${{ steps.filter.outputs.rust }}
BROWSER_CHANGED: ${{ steps.filter.outputs.browser }}
run: |
set -euo pipefail
browser=false
if [ "$LEGACY_BROWSER" = true ] && [ "$RUST_CHANGED" = true ] && [ "$BROWSER_CHANGED" = true ]; then
browser=true
fi
printf 'selected=%s\n' "$browser" >> "$GITHUB_OUTPUT"
printf '\nLegacy browser verification selected: %s\n' "$browser" >> "$GITHUB_STEP_SUMMARY"
main-policy:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Audit public checked-PR enforcement
run: python3 .github/scripts/check_main_policy.py --public-view
env:
GH_TOKEN: ${{ github.token }}
formal:
needs: changes
if: needs.changes.outputs.formal == 'true'
uses: ./.github/workflows/verification.yml
quality:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: quality
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- name: Delivery and security regression tests
run: python3 -m unittest discover -s .github/scripts -p 'test_*.py'
- name: Dead-device recovery policy counterexample
run: python3 -m unittest discover -s prototypes/device-recovery-policy -p 'test_*.py'
- name: Fixed-room disclosure broker model
run: python3 -m unittest discover -s prototypes/agent-compartment -p 'test_*.py'
- name: Private runtime measurement contracts
run: python3 -m unittest discover -s crates/vhalla-cli/tools -p 'test_*.py'
workspace-tests:
name: Workspace tests (${{ matrix.name }})
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
# `cache` names the rust-cache key: lanes that compile the same
# package set share one. Lanes run under cargo-nextest (one process per
# test, all test binaries of a lane in parallel) unless `harness` is
# libtest (see the cli and private-rooms lanes below).
include:
# vhalla-rooms-node is ~17min serial; the named long poles run in
# dedicated lanes while the residual lane --skips them, so a new
# test automatically lands in rooms-node and can never go uncovered.
- name: rooms-node-heavy-a
cache: rooms-node
packages: -p vhalla-rooms-node
args: -- unix::tests::wal_tail_stays_bounded_over_long_run unix::tests::validator_set_rotates_at_activation_boundary
- name: rooms-node-heavy-b
cache: rooms-node
packages: -p vhalla-rooms-node
args: -- unix::tests::decided_history_survives_interleaved_restarts unix::tests::sweep_decided_retires_exactly_dead_state unix::tests::tombstone_ids_stay_injected_across_drawn_slots unix::tests::wal_append_failure_safety_halts_then_recovers
- name: rooms-node
cache: rooms-node
packages: -p vhalla-rooms-node
args: -- --skip wal_tail_stays_bounded_over_long_run --skip validator_set_rotates_at_activation_boundary --skip decided_history_survives_interleaved_restarts --skip sweep_decided_retires_exactly_dead_state --skip tombstone_ids_stay_injected_across_drawn_slots --skip wal_append_failure_safety_halts_then_recovers
# The cli live-mesh suite spawns real `rooms node` subprocesses;
# its longest poles run in a dedicated lane while the residual
# lane --skips them, same split discipline as rooms-node above.
# vhalla-cli's rooms_node suite serializes its four-process mesh
# tests with an in-process mutex that nextest would not honour.
- name: cli-heavy
cache: cli
harness: libtest
packages: -p vhalla-cli
args: -- enabled::live_mesh_soak_duplicate_churn_rotation_converges enabled::live_mesh_full_restart_resumes_mid_flight enabled::live_mesh_rotates_validator_set_mid_flight
- name: cli
cache: cli
harness: libtest
packages: -p vhalla-cli
args: -- --skip enabled::live_mesh_soak_duplicate_churn_rotation_converges --skip enabled::live_mesh_full_restart_resumes_mid_flight --skip enabled::live_mesh_rotates_validator_set_mid_flight
- name: stateful-storage
cache: stateful-storage
packages: -p vhalla-rooms-consensus -p vhalla-rooms-store -p vhalla-social-store -p vhalla-discovery-store -p vhalla-identity -p vhalla-native -p vhalla-ledger
- name: headless-storage
cache: headless-storage
harness: libtest
packages: -p vhalla-direct-store -p vhalla-direct-native -p vhalla-direct-sync
# One room-activity-store test runs ~7.5 min on its own; it gets a
# lane that compiles only its crate, and public-network --skips it.
- name: public-network-heavy
cache: room-activity-store
packages: -p vhalla-room-activity-store
args: -- unix::tests::lifetime_history_exceeds_social_archive_limit_with_bounded_pages
- name: public-network
cache: public-network
packages: -p vhalla-public-protocol -p vhalla-private-protocol -p vhalla-public-client -p vhalla-public-peer -p vhalla-public-sim -p vhalla-room-activity -p vhalla-room-activity-store -p vhalla-browser-storage -p vhalla-browser-vault -p vhalla-browser
args: -- --skip unix::tests::lifetime_history_exceeds_social_archive_limit_with_bounded_pages
- name: discovery-advertisements
cache: discovery-advertisements
packages: -p arc-malachitebft-network -p arc-malachitebft-config -p arc-malachitebft-app
# The private relay HTTP tests (relay::http::tests, real iroh
# upstream) race their fixture startup when started in a fresh
# process: under nextest, shutdown(Write) hit ENOTCONN in run
# 36525563105. Keep libtest here until that fixture waits for
# readiness.
- name: private-rooms
cache: private-rooms
harness: libtest
packages: -p vhalla-private-kernel -p vhalla-private-native -p vhalla-private-relay
- name: remaining
cache: remaining
packages: -p vhalla-direct-room -p vhalla-core -p vhalla-wire -p vhalla-crypto -p vhalla-policy -p vhalla-host -p vhalla-steel-thread -p vhalla-botcaptcha -p vhalla-witness -p vhalla-rooms -p vhalla-social -p vhalla-journal -p vhalla-custody -p vhalla-transport -p vhalla-attention -p vhalla-discovery -p vhalla-session -p vhalla-retrieval -p vhalla-rooms-app -p vhalla-rooms-tui
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: tests-${{ matrix.cache }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2
if: matrix.harness != 'libtest'
with:
tool: cargo-nextest@0.9.146
# Doctests are not run by nextest or `--all-targets`; workspace-aux runs
# `cargo test --workspace --doc`.
- name: Test (nextest)
if: matrix.harness != 'libtest'
run: cargo nextest run ${{ matrix.packages }} --all-targets --all-features --locked --no-fail-fast ${{ matrix.args }}
env:
TMPDIR: ${{ runner.temp }}
- name: Test (libtest)
if: matrix.harness == 'libtest'
run: cargo test ${{ matrix.packages }} --all-targets --all-features --locked ${{ matrix.args }}
env:
TMPDIR: ${{ runner.temp }}
- name: Retain failed live-mesh soak diagnostics
if: failure() && matrix.name == 'cli-heavy'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: cli-heavy-soak-diagnostics-${{ github.run_attempt }}
path: ${{ runner.temp }}/vhalla-cli-node-soak-*
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
# Whole-workspace `cargo check` coverage: every crate compile-checks for
# x86_64-pc-windows-msvc today (Unix-specific internals are `cfg(unix)`
# modules inside portable crates, not Unix-only crates). The private client
# build below also proves the member-side `identity` + `private` surface
# links on MSVC — host, gateway and agent serving remain Unix-gated; the
# Windows release archive carries exactly that build. Test targets compile only for the
# crates whose dev-dependencies are pure Rust; the rest pull C-backed
# dev-deps (hegeltest) that this lane does not yet qualify. vhalla-custody
# runs its full lint and test suite on the Windows backend. Do not widen
# silently: only what this lane verifies belongs in it.
windows-portable:
name: Windows portable checks
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
components: clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: workspace-windows
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Custody lint and tests
run: |
cargo clippy -p vhalla-custody --all-targets --all-features --locked -- -D warnings
cargo test -p vhalla-custody --all-targets --locked
- name: Workspace compile coverage
run: cargo check --workspace --locked
- name: Private client link coverage
run: cargo build --locked -p vhalla-cli --no-default-features --features experimental-private --bin vhalla
# The Windows release ships this feature set: identity custody, the
# member side of private rooms, and a clear refusal for every
# Unix-only command (tests/windows.rs).
- name: Windows CLI tests
run: |
cargo test --locked -p vhalla-identity
cargo test --locked -p vhalla-cli --no-default-features --features experimental-private --bin vhalla --test windows
- name: site/install.ps1 installs the built vhalla.exe
shell: pwsh
run: ./.github/scripts/check_install_ps1.ps1 -Exe target/debug/vhalla.exe -Version v0.0.0
- name: Portable core test-code compile coverage
run: cargo check -p vhalla-wire -p vhalla-policy -p vhalla-witness -p vhalla-botcaptcha -p vhalla-public-protocol -p vhalla-private-protocol -p vhalla-public-client -p vhalla-room-activity -p vhalla-private-relay -p vhalla-rooms-app --all-targets --locked
workspace-aux:
name: Workspace auxiliary checks
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
components: rustfmt, clippy
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: workspace-aux
cache-all-crates: true
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Documentation and feature tests
run: |
cargo test --workspace --doc --all-features --locked
cargo test -p vhalla-cli --no-default-features --test identity --locked
cargo test -p vhalla-cli --no-default-features --test social --locked
- name: WASM pure crate checks
run: |
cargo check -p vhalla-core -p vhalla-wire -p vhalla-crypto -p vhalla-policy -p vhalla-ledger -p vhalla-session -p vhalla-social -p vhalla-discovery -p vhalla-attention -p vhalla-retrieval -p vhalla-rooms -p vhalla-rooms-app -p vhalla-journal -p vhalla-rooms-consensus -p vhalla-rooms-node -p vhalla-witness -p vhalla-botcaptcha -p vhalla-public-protocol -p vhalla-private-protocol -p vhalla-public-client -p vhalla-room-activity -p vhalla-browser-storage -p vhalla-browser-vault -p vhalla-browser --target wasm32-unknown-unknown --locked
cargo check -p vhalla-private-kernel -p vhalla-private-relay -p vhalla-browser-storage -p vhalla-browser-vault --features vhalla-browser-storage/private-rooms,vhalla-browser-vault/private-storage --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/checkpoint-proof/Cargo.toml --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/checkpoint-ledger/Cargo.toml --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/agent-portraits/Cargo.toml --lib --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/social-lifecycle/Cargo.toml --lib --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/social-reducer/Cargo.toml --lib --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/social-sync/Cargo.toml --lib --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/room-registry/Cargo.toml --lib --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/browser-records/Cargo.toml --target wasm32-unknown-unknown --locked
cargo check --manifest-path prototypes/browser-records/interop/Cargo.toml --lib --target wasm32-unknown-unknown --locked
- name: Signed discovery native and WASM execution parity
run: |
cargo install wasm-bindgen-cli --version 0.2.108 --locked
cargo fetch --manifest-path prototypes/discovery-parity/Cargo.toml --locked
sh prototypes/discovery-parity/verify.sh
- name: Independent signed facet golden vectors
run: python3 prototypes/social-facets/verify-vectors.py
- name: Independent witness encoding golden vectors
run: python3 prototypes/witness-vectors/verify-vectors.py
- name: Native checkpoint persistence
run: |
cargo test --manifest-path prototypes/checkpoint-ledger/Cargo.toml --features native-store --locked
cargo clippy --manifest-path prototypes/checkpoint-ledger/Cargo.toml --all-targets --features native-store --locked -- -D warnings
- name: Browser record fixture native checks
run: |
cargo fmt --manifest-path prototypes/browser-records/interop/Cargo.toml -- --check
cargo test --manifest-path prototypes/browser-records/interop/Cargo.toml --locked
cargo clippy --manifest-path prototypes/browser-records/interop/Cargo.toml --all-targets --locked -- -D warnings
- name: Portrait SVG vocabulary and golden vectors
run: |
cargo run --manifest-path prototypes/agent-portraits/Cargo.toml --example gallery --locked -- /tmp/valhalla-portrait-check
python3 prototypes/agent-portraits/verify.py /tmp/valhalla-portrait-check
- name: Retained native admission reference integrity
working-directory: prototypes/browser-records/interop/native-admission-reference
run: sha256sum --check SHA256SUMS
- name: DNS compatibility backport tests
run: cargo test --locked --manifest-path vendor/libp2p-dns/Cargo.toml --features tokio -- backport_
# Opt-in legacy browser qualification is four independent jobs. Only this
# one builds the production bundle, binds it to its delivery receipt, and
# uploads immutable bytes for inspection. Native releases do not consume it.
browser-artifact:
name: Browser production artifact
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.browser == 'true'
runs-on: ubuntu-latest
timeout-minutes: 40
outputs:
manifest_sha256: ${{ steps.qualified-browser.outputs.manifest_sha256 }}
artifact_id: ${{ steps.browser-upload.outputs.artifact-id }}
artifact_digest: ${{ steps.browser-upload.outputs.artifact-digest }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
targets: wasm32-unknown-unknown
components: clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: browser-artifact
cache-all-crates: true
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Browser and portable adapter lint
run: cargo clippy --locked -p vhalla-browser -p vhalla-browser-storage -p vhalla-browser-vault --features vhalla-browser/private-rooms,vhalla-browser-storage/private-rooms,vhalla-browser-vault/private-storage --target wasm32-unknown-unknown -- -D warnings
# The production artifact's Trunk is built from source on purpose
# (published bytes come from a crates.io-locked toolchain, not a
# prebuilt download); rust-cache restores ~/.cargo/bin so a warm run
# skips the compile. Qualification-only builds below use the prebuilt.
- name: Install pinned Trunk
run: cargo install trunk --locked --version 0.21.14
- name: Build production without local qualification routes
working-directory: browser
run: |
trunk --skip-version-check build --release --locked --features private-rooms
python3 tools/package.py dist
python3 -m unittest discover -s tools -p 'test_*.py'
env:
NO_COLOR: "true"
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Provision the repository's pinned Chromium and sandbox
run: bash .github/scripts/pinned_chromium.sh setup
- name: Qualification process cleanup regressions
run: node --test browser/tools/qualification_lifecycle.test.mjs browser/tools/qualification_process_guardian.test.mjs browser/tools/pinned_browser.test.mjs browser/tools/private_mixed_pilot.test.mjs browser/tools/private_generation_pilot.test.mjs
- name: Build maintained local private host and browser gateway
run: cargo build --locked -p vhalla-cli --features experimental-private
- name: Exercise production browser delivery, outage and exact restart
timeout-minutes: 8
run: node browser/tools/qualify_private_delivery.mjs "$GITHUB_WORKSPACE/browser/dist" --pinned "$RUNNER_TEMP/vhalla-private-delivery-results" "$GITHUB_WORKSPACE/target/debug/vhalla" "$(command -v openssl)"
- name: Complete private work with a browser and two native agents
run: node browser/tools/qualify_private_delivery.mjs "$GITHUB_WORKSPACE/browser/dist" --pinned "$RUNNER_TEMP/vhalla-private-mixed-results" "$GITHUB_WORKSPACE/target/debug/vhalla" "$(command -v openssl)" --mixed-pilot
- name: Exercise production private panel and archive recovery
run: node browser/tools/qualify_private_panel.mjs "$GITHUB_WORKSPACE/browser/dist" --pinned "$RUNNER_TEMP/vhalla-private-production-panel-results" --production
- name: Retain bounded browser runtime evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: vhalla-browser-runtime-evidence-${{ github.run_attempt }}
path: |
${{ runner.temp }}/vhalla-private-delivery-results/receipt.json
${{ runner.temp }}/vhalla-private-delivery-results/*.log
${{ runner.temp }}/vhalla-private-delivery-results/*.png
${{ runner.temp }}/vhalla-private-mixed-results/receipt.json
${{ runner.temp }}/vhalla-private-mixed-results/*.log
${{ runner.temp }}/vhalla-private-mixed-results/*.png
${{ runner.temp }}/vhalla-private-production-panel-results/receipt.json
${{ runner.temp }}/vhalla-private-production-panel-results/*.log
${{ runner.temp }}/vhalla-private-production-panel-results/*.png
if-no-files-found: warn
retention-days: 14
- name: Bind the upload to the successful production delivery receipt
id: qualified-browser
run: |
digest="$(python3 .github/scripts/verify_browser_artifact.py browser/dist --receipt "$RUNNER_TEMP/vhalla-private-delivery-results/receipt.json")"
printf 'manifest_sha256=%s\n' "$digest" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
id: browser-upload
with:
name: vhalla-browser-${{ github.run_attempt }}
path: browser/dist
if-no-files-found: error
retention-days: 14
- name: Remove the job-owned Chromium sandbox profile
if: always()
run: bash .github/scripts/pinned_chromium.sh cleanup
browser-worker:
name: Browser private worker journey
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.browser == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
targets: wasm32-unknown-unknown
components: clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: browser-worker
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2
with:
tool: trunk@0.21.14
- name: Check optional private room worker and local qualification entry points
run: cargo clippy --locked -p vhalla-browser --features private-rooms,local-qualification --target wasm32-unknown-unknown -- -D warnings
- name: Build isolated private room worker journey
working-directory: browser
run: |
trunk --skip-version-check build --release --locked --features private-rooms,local-qualification --dist "$RUNNER_TEMP/vhalla-private-worker-runtime"
python3 tools/package.py "$RUNNER_TEMP/vhalla-private-worker-runtime" --allow-local-qualification
env:
NO_COLOR: "true"
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Provision the repository's pinned Chromium and sandbox
run: bash .github/scripts/pinned_chromium.sh setup
- name: Exercise private worker custody, cancellation and exact reopen
run: node browser/tools/qualify_private_session.mjs "$RUNNER_TEMP/vhalla-private-worker-runtime" --pinned "$RUNNER_TEMP/vhalla-private-worker-results"
- name: Exercise private room invitations, consent and file exchange through the UI
run: node browser/tools/qualify_private_panel.mjs "$RUNNER_TEMP/vhalla-private-worker-runtime" --pinned "$RUNNER_TEMP/vhalla-private-panel-results"
- name: Retain bounded browser runtime evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: vhalla-browser-worker-evidence-${{ github.run_attempt }}
path: |
${{ runner.temp }}/vhalla-private-worker-results/receipt.json
${{ runner.temp }}/vhalla-private-worker-results/*.log
${{ runner.temp }}/vhalla-private-panel-results/receipt.json
${{ runner.temp }}/vhalla-private-panel-results/*.log
${{ runner.temp }}/vhalla-private-panel-results/*.png
if-no-files-found: warn
retention-days: 14
- name: Remove the job-owned Chromium sandbox profile
if: always()
run: bash .github/scripts/pinned_chromium.sh cleanup
browser-storage:
name: Browser storage and MLS fixtures
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.browser == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
targets: wasm32-unknown-unknown
components: clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: browser-storage
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: |
. -> target
prototypes/private-rooms-mls -> target
- name: Install exact browser bindings for runtime qualification
uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2
with:
tool: wasm-bindgen@0.2.108
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Provision the repository's pinned Chromium and sandbox
run: bash .github/scripts/pinned_chromium.sh setup
- name: Check archive and continuity receipt browser fixtures
run: cargo clippy --locked -p vhalla-browser-storage --example private_archive_indexeddb --example private_archive_recovery --example continuity_indexeddb --features private-archive-qualification,continuity-qualification --target wasm32-unknown-unknown -- -D warnings
- name: Build archive and continuity receipt browser fixtures
run: |
cargo build --locked -p vhalla-browser-storage --example private_archive_indexeddb --example private_archive_recovery --example continuity_indexeddb --features private-archive-qualification,continuity-qualification --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/private_archive_indexeddb.wasm --target web --out-name indexeddb_qualification --out-dir "$RUNNER_TEMP/vhalla-private-archive-runtime"
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/private_archive_recovery.wasm --target web --out-name archive_recovery --out-dir "$RUNNER_TEMP/vhalla-archive-recovery-runtime"
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/continuity_indexeddb.wasm --target web --out-name indexeddb_qualification --out-dir "$RUNNER_TEMP/vhalla-continuity-runtime"
- name: Exercise archive and receipt recovery in Window and dedicated worker
run: |
chrome=--pinned
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-private-archive-runtime" "$chrome"
node prototypes/browser-archive-recovery/qualify.mjs "$RUNNER_TEMP/vhalla-archive-recovery-runtime" "$chrome" "$RUNNER_TEMP/vhalla-archive-recovery-results"
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-continuity-runtime" "$chrome"
- name: Check account-derived private browser custody fixture
run: cargo clippy --locked -p vhalla-browser-storage --example private_custody_indexeddb --features private-custody-qualification --target wasm32-unknown-unknown -- -D warnings
- name: Build account-derived private browser custody fixture
run: |
cargo build --locked -p vhalla-browser-storage --example private_custody_indexeddb --features private-custody-qualification --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/private_custody_indexeddb.wasm --target web --out-name indexeddb_qualification --out-dir "$RUNNER_TEMP/vhalla-private-custody-runtime"
- name: Exercise account-derived custody in Window and dedicated worker
run: |
chrome=--pinned
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-private-custody-runtime" "$chrome"
- name: Build isolated storage and MLS worker fixtures
run: |
cargo build --locked -p vhalla-browser-storage --example indexeddb_qualification --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/indexeddb_qualification.wasm --target web --out-dir "$RUNNER_TEMP/vhalla-storage-runtime"
cargo build --locked -p vhalla-browser-storage --example private_mls_indexeddb --example private_indexeddb --features private-rooms,qualification --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/private_mls_indexeddb.wasm --target web --out-name indexeddb_qualification --out-dir "$RUNNER_TEMP/vhalla-private-group-runtime"
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/private_indexeddb.wasm --target web --out-name indexeddb_qualification --out-dir "$RUNNER_TEMP/vhalla-private-store-runtime"
cargo build --locked --manifest-path prototypes/private-rooms-mls/Cargo.toml --target wasm32-unknown-unknown
wasm-bindgen prototypes/private-rooms-mls/target/wasm32-unknown-unknown/debug/valhalla_private_rooms_mls_qualification.wasm --target web --out-name private_rooms_mls --out-dir "$RUNNER_TEMP/vhalla-mls-runtime"
- name: Exercise real IndexedDB and MLS in dedicated workers
run: |
chrome=--pinned
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-storage-runtime" "$chrome"
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-private-store-runtime" "$chrome"
node browser/tools/qualify_storage.mjs "$RUNNER_TEMP/vhalla-private-group-runtime" "$chrome"
node prototypes/private-rooms-mls/tools/qualify_browser.mjs "$RUNNER_TEMP/vhalla-mls-runtime" "$chrome" "$RUNNER_TEMP/vhalla-mls-results"
- name: Retain bounded browser runtime evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: vhalla-browser-storage-evidence-${{ github.run_attempt }}
path: |
${{ runner.temp }}/vhalla-storage-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-storage-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-private-store-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-private-store-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-private-group-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-private-group-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-private-custody-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-private-custody-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-private-archive-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-private-archive-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-continuity-runtime/indexeddb-receipt.json
${{ runner.temp }}/vhalla-continuity-runtime/chromium.stderr.log
${{ runner.temp }}/vhalla-mls-results/receipt.json
${{ runner.temp }}/vhalla-mls-results/chrome.log
if-no-files-found: warn
retention-days: 14
- name: Remove the job-owned Chromium sandbox profile
if: always()
run: bash .github/scripts/pinned_chromium.sh cleanup
browser-public:
name: Browser public journey
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.browser == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
with:
shared-key: browser-public
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2
with:
tool: trunk@0.21.14
- name: Build isolated public browser journey
run: |
# Real 20-bit discovery work keeps its fixed deadline; qualify the
# optimized fixture rather than debug SHA-256 throughput on CI hosts.
cargo build --release --locked -p vhalla-public-peer --example browser_fixture
cd browser
trunk --skip-version-check build --release --locked --features local-qualification --dist "$RUNNER_TEMP/vhalla-product-runtime"
python3 tools/package.py "$RUNNER_TEMP/vhalla-product-runtime" --allow-local-qualification
env:
NO_COLOR: "true"
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Provision the repository's pinned Chromium and sandbox
run: bash .github/scripts/pinned_chromium.sh setup
- name: Exercise scope, interrupted signing, puzzle review and peer delivery
run: node browser/tools/qualify_product.mjs "$RUNNER_TEMP/vhalla-product-runtime" "$GITHUB_WORKSPACE/target/release/examples/browser_fixture" --pinned "$RUNNER_TEMP/vhalla-product-results" --recovery
- name: Retain bounded browser runtime evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: vhalla-browser-public-evidence-${{ github.run_attempt }}
path: |
${{ runner.temp }}/vhalla-product-results/receipt.json
${{ runner.temp }}/vhalla-product-results/*.log
${{ runner.temp }}/vhalla-product-results/*.png
if-no-files-found: warn
retention-days: 14
- name: Remove the job-owned Chromium sandbox profile
if: always()
run: bash .github/scripts/pinned_chromium.sh cleanup
required:
name: Required
if: always()
needs: [changes, quality, workspace-tests, windows-portable, workspace-aux, browser-artifact, browser-worker, browser-storage, browser-public, prototype-checks, kani-spent, formal, main-policy, desktop, site, security]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require every Rust gate
env:
CHANGES: ${{ needs.changes.result }}
RUST_CHANGED: ${{ needs.changes.outputs.rust }}
FORMAL_CHANGED: ${{ needs.changes.outputs.formal }}
PROTOTYPE_LIST: ${{ needs.changes.outputs.prototypes }}
KANI_SELECTED: ${{ needs.changes.outputs.kani }}
BROWSER_SELECTED: ${{ needs.changes.outputs.browser }}
QUALITY: ${{ needs.quality.result }}
WORKSPACE_TESTS: ${{ needs.workspace-tests.result }}
WINDOWS_PORTABLE: ${{ needs.windows-portable.result }}
WORKSPACE_AUX: ${{ needs.workspace-aux.result }}
BROWSER: ${{ needs.browser-artifact.result }}
BROWSER_WORKER: ${{ needs.browser-worker.result }}
BROWSER_STORAGE: ${{ needs.browser-storage.result }}
BROWSER_PUBLIC: ${{ needs.browser-public.result }}
PROTOTYPES: ${{ needs.prototype-checks.result }}
KANI: ${{ needs.kani-spent.result }}
DESKTOP: ${{ needs.desktop.result }}
SITE: ${{ needs.site.result }}
SECURITY: ${{ needs.security.result }}
FORMAL: ${{ needs.formal.result }}
MAIN_POLICY: ${{ needs.main-policy.result }}
run: |
failed=0
# Unconditional gates must succeed.
must() {
if [ "$2" != success ]; then
echo "::error::$1 is $2"
failed=1
fi
}
# Filtered gates succeed, or skip only when the successful selection
# job reports unchanged inputs or legacy browser verification was not
# requested. A failed or cancelled selected job never counts as a skip.
filtered() {
if [ "$2" = success ]; then
return
fi
if [ "$2" = skipped ] && [ "$CHANGES" = success ] && [ "$3" = false ]; then
echo "$1 skipped: not selected for this run"
return
fi
echo "::error::$1 is $2"
failed=1
}
must changes "$CHANGES"
PROTOTYPES_SELECTED=true
if [ "$PROTOTYPE_LIST" = '[]' ]; then
PROTOTYPES_SELECTED=false
fi
must quality "$QUALITY"
must site "$SITE"
must main-policy "$MAIN_POLICY"
filtered workspace-tests "$WORKSPACE_TESTS" "$RUST_CHANGED"
filtered windows-portable "$WINDOWS_PORTABLE" "$RUST_CHANGED"
filtered workspace-aux "$WORKSPACE_AUX" "$RUST_CHANGED"
filtered browser-artifact "$BROWSER" "$BROWSER_SELECTED"
filtered browser-worker "$BROWSER_WORKER" "$BROWSER_SELECTED"
filtered browser-storage "$BROWSER_STORAGE" "$BROWSER_SELECTED"
filtered browser-public "$BROWSER_PUBLIC" "$BROWSER_SELECTED"
filtered prototype-checks "$PROTOTYPES" "$PROTOTYPES_SELECTED"
filtered kani-spent "$KANI" "$KANI_SELECTED"
filtered desktop "$DESKTOP" "$RUST_CHANGED"
filtered security "$SECURITY" "$RUST_CHANGED"
filtered formal "$FORMAL" "$FORMAL_CHANGED"
exit "$failed"
prototype-checks:
# Only the prototype crates the change can reach (see verify_scope.py);
# every top-level prototype on nightly, dispatch and release runs.
# prototypes/retired/ is manual-only.
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.prototypes != '[]'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
max-parallel: 12
matrix:
manifest: ${{ fromJSON(needs.changes.outputs.prototypes) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
with:
components: rustfmt, clippy
- name: Format
run: cargo fmt --manifest-path "${{ matrix.manifest }}" -- --check
- name: Test
run: cargo test --manifest-path "${{ matrix.manifest }}" --locked
- name: Clippy
run: cargo clippy --manifest-path "${{ matrix.manifest }}" --all-targets --locked -- -D warnings
kani-spent:
needs: changes
if: needs.changes.outputs.rust == 'true' && needs.changes.outputs.kani == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
# The kani/cargo-kani launchers are keyed by their exact version and
# saved from main only; `cargo kani setup` still installs the bundle.
- name: Restore pinned Kani launchers
id: kani-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/kani
~/.cargo/bin/cargo-kani
key: kani-verifier-0.68.0-${{ runner.os }}-${{ runner.arch }}
- name: Install pinned Kani
env:
CACHE_HIT: ${{ steps.kani-cache.outputs.cache-hit }}
run: |
if [ "$CACHE_HIT" != true ]; then
cargo install --locked --version 0.68.0 kani-verifier
fi
cargo kani setup
- name: Save pinned Kani launchers
if: github.ref == 'refs/heads/main' && steps.kani-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/kani
~/.cargo/bin/cargo-kani
key: kani-verifier-0.68.0-${{ runner.os }}-${{ runner.arch }}
- name: Fetch locked dependencies
run: cargo fetch --locked
- name: Verify bounded spent-nonce core
env:
CARGO_NET_OFFLINE: "true"
run: |
cargo kani -p vhalla-native --output-format terse -Z unstable-options --harness-timeout 300s
git diff --exit-code -- Cargo.lock
# Synthetic macOS sandbox probes for the agent compartment. The menu bar
# this job also built is retired; the job id stays so Required is unchanged.
desktop:
name: macOS agent compartment probes
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Synthetic macOS agent compartment denial probes
run: python3 prototypes/agent-compartment/qualify_macos.py
- name: Native codesign requirement parsing without private credentials
run: python3 .github/scripts/test_sign_macos_release.py NativeRequirementTests
site:
runs-on: ubuntu-latest
# Cold Chromium provisioning plus both full local runs and optional production
# verification must finish before the job limit, including evidence upload.
timeout-minutes: 22
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Install locked site dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Site tests and production build
run: bun run check:site
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '24'
# Key by the exact Playwright pin, not a moving system Chrome install.
- name: Resolve pinned Playwright cache key
id: site-playwright
run: |
version="$(node --input-type=module -e 'import pkg from "./package.json" with {type:"json"}; process.stdout.write(pkg.devDependencies["playwright-core"])')"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
- name: Restore the site qualifier's pinned Chromium
id: site-chromium-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ~/.cache/ms-playwright
key: site-chromium-${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.site-playwright.outputs.version }}
- name: Provision the site qualifier's pinned Chromium
# Always check/install the pinned full browser and Linux dependencies,
# including on a cache hit. The site never uses the headless shell.
run: bunx --no-install playwright-core install --with-deps --no-shell chromium
- name: Admit the pinned Chromium sandbox on hosted Linux
run: |
# Ubuntu restricts user namespaces for executables without an AppArmor
# profile. Admit only this job's pinned executable; keep Chromium's sandbox.
VHALLA_CI_CHROMIUM="$(node --input-type=module -e 'import {resolvePinnedBrowser} from "./site/tools/browser-contract.mjs"; process.stdout.write((await resolvePinnedBrowser()).executablePath)')"
[[ "$VHALLA_CI_CHROMIUM" =~ ^/[a-zA-Z0-9_./-]+$ ]]
VHALLA_CI_APPARMOR_PROFILE="$RUNNER_TEMP/vhalla-ci-chromium.profile"
cat > "$VHALLA_CI_APPARMOR_PROFILE" <<EOF
abi <abi/4.0>,
include <tunables/global>
profile vhalla-ci-pinned-chromium "$VHALLA_CI_CHROMIUM" flags=(unconfined) {
userns,
}
EOF
sudo apparmor_parser --replace "$VHALLA_CI_APPARMOR_PROFILE"
- name: Save the site qualifier's pinned Chromium
if: steps.site-chromium-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ~/.cache/ms-playwright
key: site-chromium-${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.site-playwright.outputs.version }}
- name: Static site responsive, keyboard and CSP qualification
run: node site/tools/qualify_browser.mjs "$GITHUB_WORKSPACE/site/dist" --pinned "$RUNNER_TEMP/vhalla-site-results" "$GITHUB_WORKSPACE/vercel.json"
- name: Every public page at phone and desktop sizes in both themes
run: node site/tools/qualify_browser.mjs "$GITHUB_WORKSPACE/site/dist" --pinned "$RUNNER_TEMP/vhalla-public-site-results" "$GITHUB_WORKSPACE/vercel.json" --public-pages
- name: Fixed production public-page verification
if: github.event_name == 'workflow_dispatch' && inputs.production
run: node site/tools/qualify_browser.mjs "$GITHUB_WORKSPACE/site/dist" --pinned "$RUNNER_TEMP/vhalla-production-site-results" "$GITHUB_WORKSPACE/vercel.json" --production
- name: Remove the job-owned Chromium sandbox profile
if: always()
run: |
VHALLA_CI_APPARMOR_PROFILE="$RUNNER_TEMP/vhalla-ci-chromium.profile"
if [ -f "$VHALLA_CI_APPARMOR_PROFILE" ]; then
sudo apparmor_parser --remove "$VHALLA_CI_APPARMOR_PROFILE"
rm "$VHALLA_CI_APPARMOR_PROFILE"
fi
- name: Retain site visual evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: vhalla-site-visual-evidence-${{ github.run_attempt }}
path: |
${{ runner.temp }}/vhalla-site-results/receipt.json
${{ runner.temp }}/vhalla-site-results/chrome.log
${{ runner.temp }}/vhalla-site-results/*.png
${{ runner.temp }}/vhalla-public-site-results/*.json
${{ runner.temp }}/vhalla-public-site-results/*.png
${{ runner.temp }}/vhalla-production-site-results/*.json
${{ runner.temp }}/vhalla-production-site-results/*.png
if-no-files-found: warn
retention-days: 14
security:
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
- name: Install pinned advisory checker
uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2
with:
tool: cargo-audit@0.22.2
- name: Audit maintained graphs and retain raw lockfile findings
run: python3 .github/scripts/audit_dependencies.py --output-dir "$RUNNER_TEMP/security-reports"
- name: Retain complete security evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: security-reports-${{ github.run_attempt }}
path: ${{ runner.temp }}/security-reports
if-no-files-found: error
retention-days: 14