Repository navigation
Expand file tree
/
Copy pathgate-nix.nix
More file actions
1508 lines (1457 loc) · 91.5 KB
/
Copy pathgate-nix.nix
File metadata and controls
1508 lines (1457 loc) · 91.5 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
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Nix-layer tests: checks that exercise the Nix compiler and install tooling.
# These stay in bash because they perform open-ended Nix builds and external
# fetches; expressing them as runtime tasks would exceed the bounded role of
# those framework tasks and blur which layer is under test.
{ pkgs, debugRuntime }:
pkgs.writeShellApplication {
name = "nixfied-gate-nix";
runtimeInputs = [
pkgs.nix
pkgs.git
pkgs.jq
pkgs.coreutils
pkgs.diffutils
pkgs.gnused
pkgs.gnutar
pkgs.gzip
];
text = ''
checkout="''${NIXFIED_GATE_CHECKOUT:-$PWD}"
dirty="''${NIXFIED_GATE_DIRTY:-}"
# Nix path inputs reject symlinked ancestors. macOS /tmp and /var are
# aliases, so every temporary fixture must start under the physical root.
TMPDIR="$(cd "''${TMPDIR:-/tmp}" && pwd -P)"
export TMPDIR
for arg in "$@"; do
case "$arg" in
--dirty) dirty=1 ;;
*) echo "gate-nix: unknown argument: $arg (supported: --dirty)" >&2; exit 1 ;;
esac
done
rt="${debugRuntime}/bin/nixfied-runtime"
fail() {
echo " GATE FAIL: $*" >&2
exit 1
}
framework_help() {
local current_system help_dir help_state lock_before expected actual
current_system=$(nix eval --impure --raw --expr builtins.currentSystem)
help_dir=$(mktemp -d)
help_state="$help_dir/state"
lock_before=$(sha256sum "$checkout/flake.lock")
expected=$(nix eval --no-write-lock-file --raw "$checkout#apps.$current_system" \
--apply "$(<"$checkout/nix/help-renderer.nix")") \
|| fail "framework help: generated root app metadata did not render"
actual=$(
cd "$help_dir"
NIXFIED_STATE_DIR="$help_state" nix run --no-write-lock-file "$checkout#help"
) || fail "framework help: explicit checkout invocation failed"
[ "$actual" = "$expected" ] \
|| fail "framework help: output did not match the generated root app metadata"
for flag in -h --help; do
actual=$(
cd "$help_dir"
NIXFIED_STATE_DIR="$help_state" nix run --no-write-lock-file "$checkout#help" -- "$flag"
) || fail "framework help: $flag failed"
[ "$actual" = "$expected" ] \
|| fail "framework help: $flag changed the catalog"
done
if (
cd "$help_dir"
nix run --no-write-lock-file "$checkout#help" -- unexpected
) >/dev/null 2>&1; then
fail "framework help: accepted an unknown argument"
fi
if (
cd "$help_dir"
nix run --no-write-lock-file "$checkout#help" -- one two
) >/dev/null 2>&1; then
fail "framework help: accepted multiple arguments"
fi
[ ! -e "$help_state" ] || fail "framework help: materialized runtime state"
[ ! -e "$help_dir/flake.lock" ] || fail "framework help: wrote a caller lock file"
[ "$(sha256sum "$checkout/flake.lock")" = "$lock_before" ] \
|| fail "framework help: modified the framework lock file"
nix eval --impure --raw --expr "
let render = import $checkout/nix/help-renderer.nix;
in render {
a = { program = \"/a\"; meta.description = \"A\"; };
bbb = { program = \"/bbb\"; meta.description = \"B\"; };
}
" >"$help_dir/renderer.actual" || fail "framework help: renderer golden did not evaluate"
printf 'Nixfied-generated commands:\n\n a A\n bbb B\n' >"$help_dir/renderer.expected"
[ "$(sha256sum <"$help_dir/renderer.actual")" = "$(sha256sum <"$help_dir/renderer.expected")" ] \
|| fail "framework help: renderer order or layout drifted"
printf '%s\n' "$actual" | grep -Eq "^ help +List Nixfied-generated commands$" \
|| fail "framework help: catalog omitted its own app"
if printf '%s\n' "$actual" | grep -Fq "nix run .#"; then
fail "framework help: catalog baked a caller-relative invocation"
fi
rm -rf "$help_dir"
}
t0=$SECONDS
echo " framework help (generated root app metadata from any cwd)" >&2
framework_help
printf ' framework_help: %ds\n' "$((SECONDS - t0))" >&2
framework_reference() {
# Gate-only fixtures copy the current supplying source, never historical work.
local work source current_system variant project expected_source executable
work=$(mktemp -d)
source=$(nix flake metadata --no-write-lock-file --json "$checkout" | jq -er .path)
current_system=$(nix eval --impure --raw --expr builtins.currentSystem)
for variant in one two; do
cp -R "$source" "$work/framework-$variant"
chmod -R u+w "$work/framework-$variant"
sed "/^## Author /a\\Revision binding fixture: source-$variant" \
"$work/framework-$variant/docs/GUIDE.md" > "$work/guide-$variant"
cat "$work/guide-$variant" > "$work/framework-$variant/docs/GUIDE.md"
project="$work/project-$variant"
mkdir "$project"
cat > "$project/flake.nix" <<FIXTURE
{
inputs.nixfied.url = "path:$work/framework-$variant";
outputs = { nixfied, ... }: {
apps.$current_system = nixfied.lib.$current_system.projectApps ./nixfied.nix;
supplyingSource = nixfied.outPath;
};
}
FIXTURE
cat > "$project/nixfied.nix" <<'MODULE'
{ ... }: {
nixfied.project.projectId = throw "docs forced invalid project identity";
nixfied.project.name = throw "docs forced invalid project name";
nixfied.closures.unused.package = throw "docs forced project executable";
nixfied.surface.verbs = { };
}
MODULE
nix flake lock "$project" >&2 || fail 'reference: downstream lock failed'
expected_source=$(nix eval --raw "$project#supplyingSource")
executable=$(nix eval --raw "$project#apps.$current_system.docs.program")
nix run --no-write-lock-file "$project#docs" -- topic authoring > "$work/topic-$variant"
grep -Fq "Revision binding fixture: source-$variant" "$work/topic-$variant" \
|| fail 'reference: downstream content did not follow its pin'
if grep -Fq '## Upgrade and recover' "$work/topic-$variant"; then
fail 'reference: downstream topic included unrelated sections'
fi
nix run --no-write-lock-file "$project#docs" -- source > "$work/source-$variant"
grep -Fxq "path: $expected_source" "$work/source-$variant" \
|| fail 'reference: provenance did not match the supplying input'
if grep -Eq '^revision:' "$work/source-$variant"; then
fail 'reference: a path source claimed a committed revision'
fi
# Invoke the already realised executable from another project: no current
# directory, Nix registry or runtime state lookup may redirect its content.
(cd "$work"; NIXFIED_STATE_DIR="$work/no-state" "$executable" source) > "$work/direct-$variant"
cmp "$work/source-$variant" "$work/direct-$variant" \
|| fail 'reference: realised docs changed source with its caller'
test ! -e "$work/no-state" || fail 'reference: docs materialised runtime state'
# Help consumes the generated app definitions. Supply valid manifest
# metadata for that separate discovery proof after the poisoned docs queries.
cat > "$project/nixfied.nix" <<'MODULE'
{ adapters, ... }: {
imports = [ adapters.synthetic ];
nixfied.project.projectId = "reference-fixture";
nixfied.project.name = "Reference fixture";
}
MODULE
(cd "$project"; nix run --no-write-lock-file .#help) > "$work/help-$variant"
grep -Eq '^ docs +' "$work/help-$variant" || fail 'reference: project help omitted docs'
done
if cmp -s "$work/source-one" "$work/source-two"; then
fail 'reference: distinct supplying sources produced identical provenance'
fi
rm -rf "$work"
}
t0=$SECONDS
echo " framework reference (pinned sources, invalid project, no runtime dependency)" >&2
framework_reference
compilation_isolation() {
local work source variant framework
work=$(mktemp -d)
source=$(nix flake metadata --no-write-lock-file --json "$checkout" | jq -er .path)
for variant in topic output; do
framework="$work/$variant"
cp -R "$source" "$framework"
chmod -R u+w "$framework"
case "$variant" in
topic) sed -i 's/^ manifest = {/ renamed-manifest = {/' "$framework/nix/docs/topics.nix" ;;
output) sed -i 's/producer = "None"/producer = "InvalidProducer"/' "$framework/nix/meta/outputs.nix" ;;
esac
nix build --no-link --impure --expr "
let f = builtins.getFlake (\"path:$framework\");
in (builtins.getAttr builtins.currentSystem f.lib).compileManifest
($framework/examples/postgres/nixfied.nix)
" || fail "compilation isolation: $variant poisoned valid compilation"
if nix eval --impure --raw --expr "
let f = builtins.getFlake (\"path:$framework\");
in (builtins.getAttr builtins.currentSystem f.packages).docs.drvPath
" >"$work/docs-$variant" 2>"$work/error-$variant"; then
fail "compilation isolation: $variant did not reject reference construction"
fi
grep -Eq 'missing or wrong-kind reference|invalid field|invalid or colliding vocabulary|invalid record' "$work/error-$variant" \
|| fail "compilation isolation: $variant failed for an unrelated reason"
if nix eval --impure --raw --expr "
let f = builtins.getFlake (\"path:$framework\");
in (builtins.getAttr builtins.currentSystem f.checks).rust-workspace.drvPath
" >"$work/check-$variant" 2>"$work/check-error-$variant"; then
fail "compilation isolation: $variant escaped release checks"
fi
done
rm -rf "$work"
}
echo " compilation isolation (invalid presentation cannot gate valid manifests)" >&2
compilation_isolation
runtime_sources() {
# Executable source-identity matrix. Only current checkout files are copied.
local work source variant product
work=$(mktemp -d)
source=$(nix flake metadata --no-write-lock-file --json "$checkout" | jq -er .path)
cat > "$work/identity.nix" <<'NIX'
{ framework, variant }:
let
flake = builtins.getFlake ("path:" + framework);
pkgs = import flake.inputs.nixpkgs {
system = builtins.currentSystem;
overlays = [ flake.inputs.rust-overlay.overlays.default ];
};
product = package:
let built = import (variant + "/nix/packages/runtime.nix") {
inherit pkgs package; source = builtins.toPath (variant + "/runtime");
};
in { source = built.src.drvPath; derivation = built.drvPath; };
in builtins.listToAttrs (map (name: { inherit name; value = product name; })
[ "nixfied-cli" "nixfied-runtime" "nixfied-test-child" ])
NIX
for variant in baseline cli runtime manifest generated runtime-generated cli-generated child reference declaration; do
mkdir -p "$work/$variant"
cp -R "$source/." "$work/$variant/"
chmod -R u+w "$work/$variant"
case "$variant" in
cli) printf '\n// CLI source variation.\n' >> "$work/$variant/runtime/crates/nixfied-cli/src/main.rs" ;;
runtime) printf '\n// Runtime source variation.\n' >> "$work/$variant/runtime/crates/nixfied-runtime/src/main.rs" ;;
manifest) printf '\n// Native manifest source variation.\n' >> "$work/$variant/runtime/crates/nixfied-manifest/src/types.rs" ;;
generated) printf '\n// Generated manifest source variation.\n' >> "$work/$variant/runtime/crates/nixfied-manifest/src/generated/types.rs" ;;
runtime-generated) printf '\n// Generated output source variation.\n' >> "$work/$variant/runtime/crates/nixfied-runtime/src/generated/error.rs" ;;
cli-generated) printf '\n// Generated CLI source variation.\n' >> "$work/$variant/runtime/crates/nixfied-cli/src/generated/commands.rs" ;;
child) printf '\n// Test child source variation.\n' >> "$work/$variant/runtime/crates/nixfied-test-child/src/main.rs" ;;
reference) printf '\nReference variation\n' >> "$work/$variant/docs/GUIDE.md" ;;
declaration) printf '\n# Declaration variation\n' >> "$work/$variant/nix/meta/manifest.nix" ;;
esac
NIXFIED_MATRIX_EXPR="$work/identity.nix" NIXFIED_MATRIX_FRAMEWORK="$source" NIXFIED_MATRIX_VARIANT="$work/$variant" \
nix eval --impure --json --expr 'import (builtins.getEnv "NIXFIED_MATRIX_EXPR") {
framework = builtins.getEnv "NIXFIED_MATRIX_FRAMEWORK";
variant = builtins.getEnv "NIXFIED_MATRIX_VARIANT";
}' > "$work/$variant.json" || fail 'source matrix evaluation failed'
done
for variant in cli runtime manifest generated runtime-generated cli-generated child reference declaration; do
for product in nixfied-cli nixfied-runtime nixfied-test-child; do
local changed=false
case "$variant:$product" in
cli:nixfied-cli|cli-generated:nixfied-cli|runtime:nixfied-runtime|manifest:nixfied-runtime|generated:nixfied-runtime|runtime-generated:nixfied-runtime|child:nixfied-test-child) changed=true ;;
esac
jq -en --arg product "$product" --argjson changed "$changed" \
--slurpfile baseline "$work/baseline.json" --slurpfile variant "$work/$variant.json" '
($baseline[0][$product].source != $variant[0][$product].source) == $changed and
($baseline[0][$product].derivation != $variant[0][$product].derivation) == $changed
' >/dev/null || fail "source isolation: $variant affected the wrong product $product"
done
done
rm -rf "$work"
}
echo " Rust product source variation matrix" >&2
runtime_sources
inventory_mutation() {
# Mutate the actual single authored inventory in an isolated current-source copy.
local work source capability
work=$(mktemp -d)
source=$(nix flake metadata --no-write-lock-file --json "$checkout" | jq -er .path)
cp -R "$source/." "$work/"
chmod -R u+w "$work"
capability=$(<"$work/runtime/crates/nixfied-manifest/capability.txt")
printf '%s\n' "''${capability/enum StdinPolicy: null inherit/enum StdinPolicy: null pipe}" \
> "$work/runtime/crates/nixfied-manifest/capability.txt"
NIXFIED_MUTATION_SOURCE="$work" nix build --impure --no-link --expr '
let
root = builtins.getEnv "NIXFIED_MUTATION_SOURCE";
f = builtins.getFlake ("path:" + root);
pkgs = import f.inputs.nixpkgs {
system = builtins.currentSystem;
overlays = [ f.inputs.rust-overlay.overlays.default ];
};
in import (root + "/nix/checks/inventory-mutation.nix") { inherit pkgs; }
' || fail 'inventory mutation: Nix/Rust projection failed'
nix run --no-write-lock-file "path:$work#docs" -- option 'nixfied.tasks.<name>.invocation.stdin' > "$work/option.txt"
grep -Fq '"pipe"' "$work/option.txt" || fail 'inventory mutation: native option reference did not change'
if grep -Fq '"inherit"' "$work/option.txt"; then fail 'inventory mutation: stale native enum members'; fi
nix run --no-write-lock-file "path:$work#docs" -- api record primitive/Invocation > "$work/record.txt"
grep -Fq 'enum StdinPolicy: null, pipe' "$work/record.txt" || fail 'inventory mutation: record reference did not change'
if grep -Fq 'enum StdinPolicy: null, inherit' "$work/record.txt"; then fail 'inventory mutation: stale record members'; fi
rm -rf "$work"
}
echo " Shared inventory mutation (Nix, Rust, reference)" >&2
inventory_mutation
printf ' framework_reference: %ds\n' "$((SECONDS - t0))" >&2
# Nix-layer validation must fail before an invalid manifest or app catalog can
# become executable. Batch the negative expressions into one evaluation.
reject_invalid_evaluations() {
local unexpected
if ! unexpected=$(nix eval --json --impure --expr "import ${./gate-nix-negatives.nix} { checkout = $checkout; }"); then
fail "negative: batched invalid evaluation failed"
fi
if [ "$unexpected" != "[]" ]; then
echo " unexpected successful negative cases:" >&2
echo "$unexpected" | jq -r '.[] | " - " + .' >&2
fail "negative: invalid expressions succeeded instead of failing at evaluation"
fi
}
t0=$SECONDS
echo " negative (invalid manifests and app metadata must fail at nix evaluation)" >&2
reject_invalid_evaluations
printf ' reject_invalid_evaluations: %ds\n' "$((SECONDS - t0))" >&2
echo " positive (immutable source dirtyPolicy reject admits)" >&2
t0=$SECONDS
immutable_manifest=$(nix build --no-link --print-out-paths --impure --expr \
"let flake = builtins.getFlake (toString $checkout); compileManifest = (builtins.getAttr builtins.currentSystem flake.lib).compileManifest; in compileManifest ({ ... }: { imports = [ $checkout/examples/minimal/nixfied.nix ]; nixfied.codebases.main.sourceMode = \"snapshot\"; nixfied.codebases.main.sourceIdentity = builtins.path { path = $checkout/examples/minimal; name = \"nixfied-minimal-source\"; }; nixfied.codebases.main.dirtyPolicy = \"reject\"; })") \
|| fail "immutable source: manifest build failed"
"$rt" check --manifest "$immutable_manifest/manifest.json" >/dev/null \
|| fail "immutable source: runtime check failed"
printf ' immutable_source: %ds\n' "$((SECONDS - t0))" >&2
t0=$SECONDS
if [ -n "$dirty" ]; then
pin="path:$checkout"
echo " pin: $pin (--dirty: every run re-derives the closure)" >&2
else
pin="git+file://$checkout?rev=$(git -C "$checkout" rev-parse HEAD)"
# Nix refuses to fetch from shallow clones (CI checkouts) unless told.
if [ "$(git -C "$checkout" rev-parse --is-shallow-repository)" = true ]; then
pin="$pin&shallow=1"
fi
if ! git -C "$checkout" diff --quiet HEAD 2>/dev/null; then
echo " pin: HEAD — uncommitted changes are NOT exercised here (use --dirty)" >&2
fi
fi
echo " adoption (#install and runtime controls against a throwaway repo)" >&2
upgrade_fixture="$checkout/nix/fixtures/upgrade-golden"
upgrade_manifest="$upgrade_fixture/manifest.json"
upgrade_expected="$upgrade_fixture/expected.diff"
upgrade_old_archive="$upgrade_fixture/nixfied-old.tar.gz"
upgrade_new_archive="$upgrade_fixture/nixfied-new.tar.gz"
upgrade_old_url="file://$upgrade_old_archive"
upgrade_new_url="file://$upgrade_new_archive"
assert_upgrade_unchanged() {
local root="$1" expected_flake="$2" expected_lock="$3" expected_project="$4" label="$5"
[ "$expected_flake" = "$(sha256sum "$root/flake.nix")" ] \
|| fail "upgrade: $label changed flake.nix"
[ "$expected_lock" = "$(sha256sum "$root/flake.lock")" ] \
|| fail "upgrade: $label changed flake.lock"
[ "$expected_project" = "$(sha256sum "$root/nixfied.nix")" ] \
|| fail "upgrade: $label changed nixfied.nix"
}
assert_evaluation_status() {
local path="$1" expected="$2"
[ "$(grep -c '^candidate manifest evaluation:' "$path")" -eq 1 ] \
|| fail "upgrade: evaluation status missing or repeated"
grep -Fxq "candidate manifest evaluation: $expected" "$path" \
|| fail "upgrade: expected evaluation status $expected"
! grep -Fq 'candidate verification:' "$path" \
|| fail "upgrade: obsolete verification status"
}
upgrade_transaction_tests() {
local project manifest_project lock_failure_project no_lock_project
local before_flake before_lock before_project bad_pin no_lock_url policy run_status
local apply_args mode_args throwing_project state unrelated_before
echo " upgrade transaction and mode semantics" >&2
missing_lock_project=$(mktemp -d)
nix run "$checkout#install" -- --root "$missing_lock_project" --project-id upgrade-missing-lock --name upgrade-missing-lock --nixfied-url "$pin" >/dev/null \
|| fail "upgrade transaction: missing-lock fixture install failed"
[ ! -e "$missing_lock_project/flake.lock" ] || fail "upgrade transaction: missing-lock fixture unexpectedly had a lock"
before_flake=$(sha256sum "$missing_lock_project/flake.nix")
before_project=$(sha256sum "$missing_lock_project/nixfied.nix")
for policy in checked plan forced; do
mode_args=()
case "$policy" in plan) mode_args=(--plan) ;; forced) mode_args=(--force) ;; esac
run_status=0
nix run "$checkout#upgrade" -- --root "$missing_lock_project" --nixfied-url "$pin" "''${mode_args[@]}" \
>"$missing_lock_project/stdout" 2>"$missing_lock_project/stderr" || run_status=$?
[ "$run_status" -eq 3 ] || fail "upgrade transaction: missing lock $policy returned $run_status"
grep -Fq "flake.lock is required for a locked upgrade" "$missing_lock_project/stderr" \
|| fail "upgrade transaction: missing lock refusal was not reported"
[ "$before_flake" = "$(sha256sum "$missing_lock_project/flake.nix")" ] \
|| fail "upgrade transaction: missing lock changed flake.nix"
[ "$before_project" = "$(sha256sum "$missing_lock_project/nixfied.nix")" ] \
|| fail "upgrade transaction: missing lock changed nixfied.nix"
[ ! -e "$missing_lock_project/flake.lock" ] || fail "upgrade transaction: missing lock was created"
[ ! -s "$missing_lock_project/stdout" ] || fail "upgrade transaction: missing lock emitted stdout"
done
rm -rf "$missing_lock_project"
for policy in checked forced; do
apply_args=()
[[ "$policy" != forced ]] || apply_args=(--force)
project=$(mktemp -d)
# A distinct pin of the current source keeps the candidate compatible
# across uncommitted API cutovers. HEAD may still expose the previous API.
race_candidate_pin="path:$(nix flake metadata --no-write-lock-file --json "$checkout" | jq -er .path)"
nix run "$checkout#install" -- --root "$project" --project-id upgrade-race --name upgrade-race --nixfied-url "$pin" >/dev/null \
|| fail "upgrade transaction: race fixture install failed"
${pkgs.gnused}/bin/sed -i \
-e '/nixfied.url = /a\ unrelated.url = "path:${pkgs.path}";\n alias.follows = "unrelated";\n nixfied.inputs.nixpkgs.follows = "unrelated";' \
-e 's/{ self, nixfied }/{ self, nixfied, ... }/' "$project/flake.nix"
nix flake lock "$project" >/dev/null || fail "upgrade transaction: race fixture lock failed"
unrelated_before=$(jq -Sc '.nodes[.nodes[.root].inputs.unrelated].locked' "$project/flake.lock")
before_flake=$(sha256sum "$project/flake.nix")
before_lock=$(sha256sum "$project/flake.lock")
before_project=$(sha256sum "$project/nixfied.nix")
race_state="$project/runtime-state"
NIXFIED_STATE_DIR="$race_state" NIXFIED_UPGRADE_TEST_PAUSE_BEFORE_APPLY=60 nix run "$checkout#upgrade" -- \
--root "$project" --nixfied-url "$race_candidate_pin" "''${apply_args[@]}" \
>"$project/race-one.stdout" 2>"$project/race-one.stderr" &
race_one=$!
race_paused=0
for attempt in $(seq 1 720); do
: "$attempt"
if grep -Fq "test pause before apply" "$project/race-one.stderr"; then
race_paused=1
break
fi
kill -0 "$race_one" 2>/dev/null || break
sleep 0.25
done
if [ "$race_paused" -ne 1 ]; then
kill "$race_one" 2>/dev/null || true
fail "upgrade transaction: concurrent fixture never reached the guarded pre-apply window"
fi
NIXFIED_STATE_DIR="$race_state" nix run "$checkout#upgrade" -- --root "$project" \
--nixfied-url "$race_candidate_pin" "''${apply_args[@]}" >"$project/race-two.stdout" 2>"$project/race-two.stderr" &
race_two=$!
race_one_status=0
race_two_status=0
wait "$race_one" || race_one_status=$?
wait "$race_two" || race_two_status=$?
[ "$race_one_status" -eq 0 ] || [ "$race_two_status" -eq 0 ] \
|| fail "upgrade transaction: concurrent upgrades both failed"
[ "$race_one_status" -eq 6 ] || [ "$race_two_status" -eq 6 ] \
|| fail "upgrade transaction: concurrent upgrade did not refuse the stale candidate"
grep -hEq "changed concurrently|concurrently|changed while the candidate was being prepared" "$project/race-one.stderr" "$project/race-two.stderr" \
|| fail "upgrade transaction: concurrent upgrade omitted the conflict diagnostic"
[ "$before_project" = "$(sha256sum "$project/nixfied.nix")" ] \
|| fail "upgrade transaction: concurrent upgrade changed nixfied.nix"
[ "$before_flake" != "$(sha256sum "$project/flake.nix")" ] \
|| fail "upgrade transaction: concurrent upgrade did not apply flake.nix"
[ "$before_lock" != "$(sha256sum "$project/flake.lock")" ] \
|| fail "upgrade transaction: concurrent upgrade did not apply flake.lock"
grep -Fq "$race_candidate_pin" "$project/flake.nix" \
|| fail "upgrade transaction: concurrent upgrade applied the wrong flake pin"
grep -Fq ' type: path' "$project/race-one.stderr" "$project/race-two.stderr" \
|| fail "upgrade transaction: concurrent path candidate identity was not reported"
[ "$unrelated_before" = "$(jq -Sc '.nodes[.nodes[.root].inputs.unrelated].locked' "$project/flake.lock")" ] \
|| fail "upgrade transaction: $policy changed unrelated effective input identity"
jq -e '.nodes[.root].inputs.alias == ["unrelated"]
and .nodes[.nodes[.root].inputs.nixfied].inputs.nixpkgs == ["unrelated"]' "$project/flake.lock" >/dev/null \
|| fail "upgrade transaction: $policy changed follows wiring"
if [[ "$race_one_status" -eq 6 ]]; then
! grep -q '^candidate manifest evaluation:' "$project/race-one.stderr" || fail "upgrade transaction: conflict reported completion"
else
! grep -q '^candidate manifest evaluation:' "$project/race-two.stderr" || fail "upgrade transaction: conflict reported completion"
fi
[ -z "$(find "$project" -maxdepth 1 -type f -name '*nixfied-upgrade*' -print -quit)" ] \
|| fail "upgrade transaction: concurrent upgrade left temporary files"
[ ! -e "$race_state" ] || fail "upgrade transaction: concurrent upgrade materialized runtime state"
rm -rf "$project"
interrupt_project=$(mktemp -d)
nix run "$checkout#install" -- --root "$interrupt_project" --project-id upgrade-interrupt --name upgrade-interrupt --nixfied-url "$pin" >/dev/null \
|| fail "upgrade transaction: interrupt fixture install failed"
nix flake lock "$interrupt_project" >/dev/null || fail "upgrade transaction: interrupt fixture lock failed"
before_flake=$(sha256sum "$interrupt_project/flake.nix")
before_lock=$(sha256sum "$interrupt_project/flake.lock")
before_project=$(sha256sum "$interrupt_project/nixfied.nix")
state="$interrupt_project/runtime-state"
NIXFIED_STATE_DIR="$state" NIXFIED_UPGRADE_TEST_PAUSE_AFTER_LOCK=30 nix run "$checkout#upgrade" -- \
--root "$interrupt_project" --nixfied-url "$race_candidate_pin" "''${apply_args[@]}" \
>"$interrupt_project/stdout" 2>"$interrupt_project/stderr" &
interrupt_pid=$!
paused=0
for attempt in $(seq 1 720); do
: "$attempt"
if grep -Fq "test pause after lock apply" "$interrupt_project/stderr"; then
paused=1
break
fi
kill -0 "$interrupt_pid" 2>/dev/null || break
sleep 0.25
done
[ "$paused" -eq 1 ] || fail "upgrade transaction: interrupt fixture never reached the guarded apply window"
kill -TERM "$interrupt_pid"
interrupt_status=0
wait "$interrupt_pid" || interrupt_status=$?
[ "$interrupt_status" -eq 130 ] || fail "upgrade transaction: interrupted $policy returned $interrupt_status"
grep -Fq "upgrade interrupted; candidate rolled back" "$interrupt_project/stderr" \
|| fail "upgrade transaction: interrupted apply omitted rollback status"
[ "$before_flake" = "$(sha256sum "$interrupt_project/flake.nix")" ] \
|| fail "upgrade transaction: interrupted apply changed flake.nix"
[ "$before_lock" = "$(sha256sum "$interrupt_project/flake.lock")" ] \
|| fail "upgrade transaction: interrupted apply changed flake.lock"
[ "$before_project" = "$(sha256sum "$interrupt_project/nixfied.nix")" ] \
|| fail "upgrade transaction: interrupted apply changed nixfied.nix"
[ -z "$(find "$interrupt_project" -maxdepth 1 -type f -name '*nixfied-upgrade*' -print -quit)" ] \
|| fail "upgrade transaction: interrupted apply left temporary files"
[ ! -e "$state" ] || fail "upgrade transaction: interrupted apply materialized runtime state"
! grep -q '^candidate manifest evaluation:' "$interrupt_project/stderr" \
|| fail "upgrade transaction: interruption emitted a completed result"
rm -rf "$interrupt_project"
done
manifest_project=$(mktemp -d)
nix run "$checkout#install" -- --root "$manifest_project" --project-id upgrade-manifest-failure --name upgrade-manifest-failure --nixfied-url "$pin" >/dev/null || fail "upgrade transaction: manifest fixture install failed"
nix flake lock "$manifest_project" >/dev/null || fail "upgrade transaction: manifest fixture lock failed"
${pkgs.gnused}/bin/sed -i '/^ nixfied.surface.verbs.smoke = /c\ nixfied.surface.verbs = [ "smoke" ];' "$manifest_project/nixfied.nix"
before_flake=$(sha256sum "$manifest_project/flake.nix")
before_lock=$(sha256sum "$manifest_project/flake.lock")
before_project=$(sha256sum "$manifest_project/nixfied.nix")
run_status=0
nix run "$checkout#upgrade" -- --root "$manifest_project" --nixfied-url "$race_candidate_pin" \
>"$manifest_project/stdout" 2>"$manifest_project/stderr" || run_status=$?
[ "$run_status" -eq 5 ] || fail "upgrade transaction: incompatible candidate returned $run_status"
assert_evaluation_status "$manifest_project/stderr" failed
grep -Fq "upgrade applied: no" "$manifest_project/stderr" || fail "upgrade transaction: manifest failure omitted apply status"
grep -Fq "upgrade not applied; no project files were changed" "$manifest_project/stderr" || fail "upgrade transaction: manifest failure omitted no-mutation status"
[ "$before_flake" = "$(sha256sum "$manifest_project/flake.nix")" ] || fail "upgrade transaction: manifest failure changed flake.nix"
[ "$before_lock" = "$(sha256sum "$manifest_project/flake.lock")" ] || fail "upgrade transaction: manifest failure changed flake.lock"
[ "$before_project" = "$(sha256sum "$manifest_project/nixfied.nix")" ] || fail "upgrade transaction: manifest failure changed nixfied.nix"
grep -Fq 'blocked: flake.nix' "$manifest_project/stderr" || fail "upgrade transaction: failure omitted blocked change"
grep -Fq 'error:' "$manifest_project/stderr" || fail "upgrade transaction: failure lost Nix diagnostic"
nix run "$checkout#upgrade" -- --root "$manifest_project" --nixfied-url "$race_candidate_pin" --plan \
>"$manifest_project/plan.stdout" 2>"$manifest_project/plan.stderr" || fail "upgrade transaction: incompatible plan failed"
assert_evaluation_status "$manifest_project/plan.stderr" 'not run (--plan)'
assert_upgrade_unchanged "$manifest_project" "$before_flake" "$before_lock" "$before_project" 'incompatible plan'
state="$manifest_project/runtime-state"
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- --root "$manifest_project" --nixfied-url "$race_candidate_pin" --force \
>"$manifest_project/force.stdout" 2>"$manifest_project/force.stderr" || fail "upgrade transaction: incompatible force failed"
assert_evaluation_status "$manifest_project/force.stderr" 'skipped (--force)'
cmp "$manifest_project/plan.stdout" "$manifest_project/force.stdout"
[ "$before_project" = "$(sha256sum "$manifest_project/nixfied.nix")" ] || fail "upgrade transaction: force changed declarations"
grep -Fq "$race_candidate_pin" "$manifest_project/flake.nix" || fail "upgrade transaction: force did not repin"
[ "$before_lock" != "$(sha256sum "$manifest_project/flake.lock")" ] || fail "upgrade transaction: force did not update lock"
[ ! -e "$state" ] || fail "upgrade transaction: force materialized runtime state"
rm -rf "$manifest_project"
# Input resolution remains valid while every attempt to demand outputs
# throws. This proves bypass independently of the reporter's status words.
throwing_project=$(mktemp -d)
cat >"$throwing_project/flake.nix" <<FIXTURE
{
inputs.nixfied.url = "$pin";
outputs = _: throw "NIXFIED_UPGRADE_OUTPUT_EVALUATED";
}
FIXTURE
printf '%s\n' '# Project-owned declaration sentinel' >"$throwing_project/nixfied.nix"
nix flake lock "$throwing_project" >/dev/null || fail "upgrade transaction: throwing fixture lock failed"
before_flake=$(sha256sum "$throwing_project/flake.nix")
before_lock=$(sha256sum "$throwing_project/flake.lock")
before_project=$(sha256sum "$throwing_project/nixfied.nix")
state="$throwing_project/runtime-state"
for policy in plan force-plan plan-force; do
case "$policy" in
plan) mode_args=(--plan) ;;
force-plan) mode_args=(--force --plan --force) ;;
plan-force) mode_args=(--plan --force --plan) ;;
esac
NIXFIED_STATE_DIR="$state" NIXFIED_UPGRADE_TEST_PAUSE_BEFORE_APPLY=1 nix run "$checkout#upgrade" -- \
--root "$throwing_project" --nixfied-url "$race_candidate_pin" "''${mode_args[@]}" \
>"$throwing_project/$policy.stdout" 2>"$throwing_project/$policy.stderr" || fail "upgrade transaction: throwing $policy failed"
assert_evaluation_status "$throwing_project/$policy.stderr" 'not run (--plan)'
! grep -Eq 'NIXFIED_UPGRADE_OUTPUT_EVALUATED|test pause before apply' "$throwing_project/$policy.stderr" \
|| fail "upgrade transaction: plan reached evaluation or apply hooks"
assert_upgrade_unchanged "$throwing_project" "$before_flake" "$before_lock" "$before_project" "$policy"
cmp "$throwing_project/plan.stdout" "$throwing_project/$policy.stdout"
cmp "$throwing_project/plan.stderr" "$throwing_project/$policy.stderr"
done
run_status=0
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- --root "$throwing_project" --nixfied-url "$race_candidate_pin" \
>"$throwing_project/checked.stdout" 2>"$throwing_project/checked.stderr" || run_status=$?
[ "$run_status" -eq 5 ] || fail "upgrade transaction: throwing checked apply returned $run_status"
grep -Fq 'NIXFIED_UPGRADE_OUTPUT_EVALUATED' "$throwing_project/checked.stderr" || fail "upgrade transaction: checked apply lost throw marker"
assert_evaluation_status "$throwing_project/checked.stderr" failed
assert_upgrade_unchanged "$throwing_project" "$before_flake" "$before_lock" "$before_project" 'throwing rejection'
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- --root "$throwing_project" --nixfied-url "$race_candidate_pin" --force --force \
>"$throwing_project/forced.stdout" 2>"$throwing_project/forced.stderr" || fail "upgrade transaction: throwing force failed"
assert_evaluation_status "$throwing_project/forced.stderr" 'skipped (--force)'
! grep -Fq 'NIXFIED_UPGRADE_OUTPUT_EVALUATED' "$throwing_project/forced.stderr" || fail "upgrade transaction: force evaluated outputs"
cmp "$throwing_project/plan.stdout" "$throwing_project/forced.stdout"
grep -Fq "$race_candidate_pin" "$throwing_project/flake.nix" || fail "upgrade transaction: throwing force did not repin"
[ "$before_lock" != "$(sha256sum "$throwing_project/flake.lock")" ] || fail "upgrade transaction: throwing force did not update lock"
[ "$before_project" = "$(sha256sum "$throwing_project/nixfied.nix")" ] || fail "upgrade transaction: throwing force edited declarations"
before_flake=$(sha256sum "$throwing_project/flake.nix")
before_lock=$(sha256sum "$throwing_project/flake.lock")
# Even unchanged candidate bytes must still evaluate in checked apply.
run_status=0
nix run "$checkout#upgrade" -- --root "$throwing_project" --nixfied-url "$race_candidate_pin" \
>"$throwing_project/noop.stdout" 2>"$throwing_project/noop.stderr" || run_status=$?
[ "$run_status" -eq 5 ] || fail "upgrade transaction: throwing checked no-op returned $run_status"
grep -Fq 'NIXFIED_UPGRADE_OUTPUT_EVALUATED' "$throwing_project/noop.stderr" || fail "upgrade transaction: checked no-op skipped evaluation"
assert_evaluation_status "$throwing_project/noop.stderr" failed
assert_upgrade_unchanged "$throwing_project" "$before_flake" "$before_lock" "$before_project" 'throwing no-op rejection'
[ ! -e "$state" ] || fail "upgrade transaction: throwing modes materialized runtime state"
rm -rf "$throwing_project"
lock_failure_project=$(mktemp -d)
nix run "$checkout#install" -- --root "$lock_failure_project" --project-id upgrade-lock-failure --name upgrade-lock-failure --nixfied-url "$pin" >/dev/null || fail "upgrade transaction: lock failure fixture install failed"
nix flake lock "$lock_failure_project" >/dev/null || fail "upgrade transaction: lock failure fixture lock failed"
bad_pin="path:$lock_failure_project/missing-nixfied-source"
before_flake=$(sha256sum "$lock_failure_project/flake.nix")
before_lock=$(sha256sum "$lock_failure_project/flake.lock")
before_project=$(sha256sum "$lock_failure_project/nixfied.nix")
for policy in checked plan forced; do
mode_args=()
case "$policy" in plan) mode_args=(--plan) ;; forced) mode_args=(--force) ;; esac
run_status=0
nix run "$checkout#upgrade" -- --root "$lock_failure_project" --nixfied-url "$bad_pin" "''${mode_args[@]}" \
>"$lock_failure_project/stdout" 2>"$lock_failure_project/stderr" || run_status=$?
[ "$run_status" -eq 4 ] || fail "upgrade transaction: lock failure $policy returned $run_status"
grep -Fq "candidate lock resolution failed" "$lock_failure_project/stderr" || fail "upgrade transaction: lock failure was not reported"
[ "$before_flake" = "$(sha256sum "$lock_failure_project/flake.nix")" ] || fail "upgrade transaction: lock failure changed flake.nix"
[ "$before_lock" = "$(sha256sum "$lock_failure_project/flake.lock")" ] || fail "upgrade transaction: lock failure changed flake.lock"
[ "$before_project" = "$(sha256sum "$lock_failure_project/nixfied.nix")" ] || fail "upgrade transaction: lock failure changed nixfied.nix"
[ ! -s "$lock_failure_project/stdout" ] || fail "upgrade transaction: lock failure emitted partial diff"
done
rm -rf "$lock_failure_project"
no_lock_project=$(mktemp -d)
nix run "$checkout#install" -- --root "$no_lock_project" --project-id upgrade-no-lock --name upgrade-no-lock --nixfied-url "$pin" >/dev/null || fail "upgrade transaction: no-lock fixture install failed"
nix flake lock "$no_lock_project" >/dev/null || fail "upgrade transaction: no-lock fixture lock failed"
no_lock_url="$pin"
before_lock=$(sha256sum "$no_lock_project/flake.lock")
before_project=$(sha256sum "$no_lock_project/nixfied.nix")
nix run "$checkout#upgrade" -- --root "$no_lock_project" --nixfied-url "$no_lock_url" --no-lock --force >"$no_lock_project/stdout" 2>"$no_lock_project/stderr" || fail "upgrade transaction: --no-lock failed"
grep -Fq "documentation diff: skipped (--no-lock; no candidate lock was produced)" "$no_lock_project/stderr" || fail "upgrade transaction: --no-lock omitted documentation skip"
grep -Fq "candidate verification: skipped (--no-lock)" "$no_lock_project/stderr" || fail "upgrade transaction: --no-lock omitted verification skip"
grep -Fq "upgrade applied: no (flake.nix already matched)" "$no_lock_project/stderr" || fail "upgrade transaction: --no-lock omitted already-matched status"
grep -Fq "unchanged: flake.nix" "$no_lock_project/stderr" || fail "upgrade transaction: --no-lock omitted unchanged flake status"
grep -Fq "preserved: nixfied.nix (project-owned)" "$no_lock_project/stderr" || fail "upgrade transaction: --no-lock omitted project ownership"
[ "$before_lock" = "$(sha256sum "$no_lock_project/flake.lock")" ] || fail "upgrade transaction: --no-lock changed flake.lock"
[ "$before_project" = "$(sha256sum "$no_lock_project/nixfied.nix")" ] || fail "upgrade transaction: --no-lock changed nixfied.nix"
rm -rf "$no_lock_project"
printf ' upgrade_transaction: ok\n' >&2
}
upgrade_transaction_tests
${import ./checks/upgrade-selection.nix { inherit pkgs; }}/bin/nixfied-upgrade-selection-tests
upgrade_versioned_tests() {
local source_root old_source new_source git_root old_git_source new_git_source
local unavailable_source old_path_pin new_path_pin old_git_pin new_git_pin
local old_git_rev new_git_rev
local failure_project unsupported_project success_project path_project git_project unavailable_project
local scope_root scope_old_source scope_new_source scope_project
local scope_old_patch scope_new_patch scope_expected scope_expected_hash
local before_flake before_lock before_project state run_status expected_empty
local old_commit new_commit old_archive_hash new_archive_hash expected_diff_hash
local policy mode_args expected_status
prepare_historical_project() {
# These frozen inputs predate the manifest API. Adapt only the throwaway
# adopter flake to expose the current evaluation package name using the
# historical compiler; never rewrite archives or documentation goldens.
${pkgs.gnused}/bin/sed -i \
-e 's/compileManifest/compileModel/g' \
-e 's/\.model/\.manifest/g' \
-e 's/ model =/ manifest =/' "$1/flake.nix"
}
sha256_file() {
sha256sum "$1" | cut -d ' ' -f1
}
assert_upgrade_golden() {
local output="$1" label="$2"
if ! cmp -s "$upgrade_expected" "$output"; then
echo " golden mismatch: $label" >&2
diff -u "$upgrade_expected" "$output" >&2 || true
fail "upgrade golden: $label differed from expected.diff"
fi
}
assert_blank_after() {
local path="$1" marker="$2" label="$3" line found=0
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$found" -eq 1 ]]; then
[[ -z "$line" ]] \
|| fail "upgrade golden: $label did not separate status sections"
return 0
fi
if [[ "$line" == "$marker" ]]; then
found=1
fi
done <"$path"
fail "upgrade golden: $label status marker was missing"
}
make_git_fixture() {
local archive="$1" destination="$2"
mkdir "$destination"
tar -xzf "$archive" -C "$destination" \
|| fail "upgrade golden: Git fixture extraction failed"
git -C "$destination" init -q
git -C "$destination" config user.email gate@nixfied
git -C "$destination" config user.name "nixfied gate"
git -C "$destination" add -A
GIT_AUTHOR_DATE=2000-01-01T00:00:00Z GIT_COMMITTER_DATE=2000-01-01T00:00:00Z \
git -C "$destination" commit -q -m fixture
}
echo " upgrade pinned versions and documentation golden" >&2
[ -f "$upgrade_manifest" ] || fail "upgrade golden: manifest is missing"
[ -f "$upgrade_expected" ] || fail "upgrade golden: expected diff is missing"
[ -f "$upgrade_old_archive" ] || fail "upgrade golden: old archive is missing"
[ -f "$upgrade_new_archive" ] || fail "upgrade golden: new archive is missing"
scope_old_patch="$upgrade_fixture/scope-old.patch"
scope_new_patch="$upgrade_fixture/scope-new.patch"
scope_expected="$upgrade_fixture/scope.expected.diff"
[ -f "$scope_old_patch" ] || fail "upgrade golden: scope old patch is missing"
[ -f "$scope_new_patch" ] || fail "upgrade golden: scope new patch is missing"
[ -f "$scope_expected" ] || fail "upgrade golden: scope expected diff is missing"
jq -e '
.archiveFormat == "tar.gz"
and .normalization.sort == "name"
and .normalization.mtime == "1970-01-01T00:00:00Z"
and .normalization.owner == 0
and .normalization.group == 0
and .normalization.numericOwner == true
and .normalization.gzip == "-n"
' "$upgrade_manifest" >/dev/null \
|| fail "upgrade golden: manifest normalization is not deterministic"
old_commit=$(jq -er '.old.commit' "$upgrade_manifest") \
|| fail "upgrade golden: old commit is missing"
new_commit=$(jq -er '.new.commit' "$upgrade_manifest") \
|| fail "upgrade golden: new commit is missing"
old_archive_hash=$(jq -er '.old.archiveSha256' "$upgrade_manifest") \
|| fail "upgrade golden: old archive hash is missing"
new_archive_hash=$(jq -er '.new.archiveSha256' "$upgrade_manifest") \
|| fail "upgrade golden: new archive hash is missing"
upgrade_old_nar_hash=$(jq -er '.old.narHash' "$upgrade_manifest") \
|| fail "upgrade golden: old NAR hash is missing"
upgrade_new_nar_hash=$(jq -er '.new.narHash' "$upgrade_manifest") \
|| fail "upgrade golden: new NAR hash is missing"
expected_diff_hash=$(jq -er '.expectedDiffSha256' "$upgrade_manifest") \
|| fail "upgrade golden: expected diff hash is missing"
scope_expected_hash=$(jq -er '.scopeExpectedDiffSha256' "$upgrade_manifest") \
|| fail "upgrade golden: scope expected diff hash is missing"
[ "$old_commit" = "8e1fa56d9423b93b89f9aabf4be86c2081da0362" ] \
|| fail "upgrade golden: old source commit drifted"
[ "$new_commit" = "0aaf8740e7ef8aa5ee487085bac7be0dd5998007" ] \
|| fail "upgrade golden: new source commit drifted"
[ "$old_archive_hash" = "013005b8e74778217891ae7f5199191eafef1edd7ec4ea2430389bf262349c0c" ] \
|| fail "upgrade golden: old archive provenance drifted"
[ "$new_archive_hash" = "b63ce314aef0f41b7394ff7388e1ba32b46f6db0c8ecfc105c393a9705de183d" ] \
|| fail "upgrade golden: new archive provenance drifted"
[ "$(sha256_file "$upgrade_old_archive")" = "$old_archive_hash" ] \
|| fail "upgrade golden: old archive checksum mismatch"
[ "$(sha256_file "$upgrade_new_archive")" = "$new_archive_hash" ] \
|| fail "upgrade golden: new archive checksum mismatch"
[ "$(sha256_file "$upgrade_expected")" = "$expected_diff_hash" ] \
|| fail "upgrade golden: expected diff checksum mismatch"
[ "$scope_expected_hash" = "7d8ab0971df7d8f494aeff47800b3dece6e20638e97f44397935ec136db31589" ] \
|| fail "upgrade golden: scope expected diff provenance drifted"
[ "$(sha256_file "$scope_expected")" = "$scope_expected_hash" ] \
|| fail "upgrade golden: scope expected diff checksum mismatch"
failure_project=$(mktemp -d)
nix run "$upgrade_old_url#install" -- \
--root "$failure_project" --project-id upgrade-golden-failure --name upgrade-golden-failure \
--nixfied-url "$upgrade_old_url" >/dev/null \
|| fail "upgrade golden: historical install failed"
grep -Fq 'nixfied.surface.verbs = [ "smoke" ];' "$failure_project/nixfied.nix" \
|| fail "upgrade golden: historical installer did not produce the list-form declaration"
# First inspect the unmodified historical API/package names. Resolution
# must not require adaptation of the adopter's outputs to the current API.
nix flake lock "$failure_project" >/dev/null \
|| fail "upgrade golden: historical failure fixture lock failed"
jq -e --arg expected "$upgrade_old_nar_hash" \
'.nodes[.nodes[.root].inputs.nixfied].locked.narHash == $expected' \
"$failure_project/flake.lock" >/dev/null \
|| fail "upgrade golden: historical failure fixture did not lock the pinned old source"
before_flake=$(sha256sum "$failure_project/flake.nix")
before_lock=$(sha256sum "$failure_project/flake.lock")
before_project=$(sha256sum "$failure_project/nixfied.nix")
state="$failure_project/runtime-state"
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$failure_project" --nixfied-url "$upgrade_new_url" --plan
10BC8
\
>"$failure_project/old-api.stdout" 2>"$failure_project/old-api.stderr" \
|| fail "upgrade golden: old API plan failed"
assert_upgrade_golden "$failure_project/old-api.stdout" 'old API plan'
assert_evaluation_status "$failure_project/old-api.stderr" 'not run (--plan)'
grep -Fq 'would change: flake.lock' "$failure_project/old-api.stderr" || fail "upgrade golden: old API plan omitted changes"
grep -Fxq 'next:' "$failure_project/old-api.stderr" || fail "upgrade golden: old API plan omitted next steps"
assert_upgrade_unchanged "$failure_project" "$before_flake" "$before_lock" "$before_project" 'old API plan'
prepare_historical_project "$failure_project"
before_flake=$(sha256sum "$failure_project/flake.nix")
run_status=0
( NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$failure_project" --nixfied-url "$upgrade_new_url" --plan \
>"$failure_project/plan.stdout" 2>"$failure_project/plan.stderr" ) \
|| run_status=$?
[ "$run_status" -eq 0 ] || fail "upgrade golden: incompatible plan returned $run_status instead of 0"
assert_upgrade_golden "$failure_project/plan.stdout" "incompatible plan"
grep -Fq ' type: tarball' "$failure_project/plan.stderr" \
|| fail "upgrade golden: tarball identity was not reported for the incompatible plan"
grep -Fq " narHash: $upgrade_old_nar_hash" "$failure_project/plan.stderr" \
|| fail "upgrade golden: old tarball NAR hash was not reported"
grep -Fq " narHash: $upgrade_new_nar_hash" "$failure_project/plan.stderr" \
|| fail "upgrade golden: candidate tarball NAR hash was not reported"
assert_evaluation_status "$failure_project/plan.stderr" 'not run (--plan)'
grep -Fq 'upgrade applied: no' "$failure_project/plan.stderr" \
|| fail "upgrade golden: incompatible plan omitted apply status"
grep -Fq 'plan: no project files changed' "$failure_project/plan.stderr" \
|| fail "upgrade golden: incompatible plan omitted no-mutation status"
grep -Fq 'would change: flake.nix' "$failure_project/plan.stderr" || fail "upgrade golden: incompatible plan omitted URL change"
grep -Fq 'would change: flake.lock' "$failure_project/plan.stderr" || fail "upgrade golden: incompatible plan omitted lock change"
grep -Fq 'post-upgrade validation: not run (--plan)' "$failure_project/plan.stderr" || fail "upgrade golden: incompatible plan omitted validation status"
assert_upgrade_unchanged "$failure_project" "$before_flake" "$before_lock" "$before_project" "incompatible plan"
[ ! -e "$state" ] || fail "upgrade golden: incompatible plan materialized runtime state"
run_status=0
( NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$failure_project" --nixfied-url "$upgrade_new_url" \
>"$failure_project/apply.stdout" 2>"$failure_project/apply.stderr" ) \
|| run_status=$?
[ "$run_status" -eq 5 ] || fail "upgrade golden: incompatible apply returned $run_status instead of 5"
assert_upgrade_golden "$failure_project/apply.stdout" "incompatible apply"
assert_evaluation_status "$failure_project/apply.stderr" failed
grep -Fq 'upgrade applied: no' "$failure_project/apply.stderr" \
|| fail "upgrade golden: incompatible apply omitted apply status"
grep -Fq 'upgrade not applied; no project files were changed' "$failure_project/apply.stderr" \
|| fail "upgrade golden: incompatible apply omitted no-mutation status"
assert_upgrade_unchanged "$failure_project" "$before_flake" "$before_lock" "$before_project" "incompatible apply"
[ ! -e "$state" ] || fail "upgrade golden: incompatible apply materialized runtime state"
grep -Fq 'error:' "$failure_project/apply.stderr" || fail "upgrade golden: checked rejection lost Nix diagnostic"
grep -Fq 'blocked: flake.lock' "$failure_project/apply.stderr" || fail "upgrade golden: checked rejection omitted blocked changes"
grep -Fq -- '--force and repair afterward' "$failure_project/apply.stderr" || fail "upgrade golden: rejection omitted explicit force guidance"
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$failure_project" --nixfied-url "$upgrade_new_url" --force \
>"$failure_project/force.stdout" 2>"$failure_project/force.stderr" || fail "upgrade golden: incompatible force failed"
assert_upgrade_golden "$failure_project/force.stdout" 'incompatible force'
assert_evaluation_status "$failure_project/force.stderr" 'skipped (--force)'
grep -Fq 'upgrade applied: yes' "$failure_project/force.stderr" || fail "upgrade golden: force omitted apply status"
grep -Fq "$upgrade_new_url" "$failure_project/flake.nix" || fail "upgrade golden: force did not apply candidate URL"
jq -e --arg expected "$upgrade_new_nar_hash" \
'.nodes[.nodes[.root].inputs.nixfied].locked.narHash == $expected' "$failure_project/flake.lock" >/dev/null \
|| fail "upgrade golden: force did not apply candidate lock"
[ "$before_project" = "$(sha256sum "$failure_project/nixfied.nix")" ] || fail "upgrade golden: force edited historical declaration"
[ ! -e "$state" ] || fail "upgrade golden: force materialized runtime state"
rm -rf "$failure_project"
unsupported_project=$(mktemp -d)
nix run "$upgrade_old_url#install" -- \
--root "$unsupported_project" --project-id upgrade-golden-unsupported --name upgrade-golden-unsupported \
--nixfied-url "$upgrade_old_url" >/dev/null \
|| fail "upgrade golden: unsupported identity install failed"
prepare_historical_project "$unsupported_project"
nix flake lock "$unsupported_project" >/dev/null \
|| fail "upgrade golden: unsupported identity fixture lock failed"
jq '.nodes[.nodes[.root].inputs.nixfied].locked.type = "sourcehut"' \
"$unsupported_project/flake.lock" >"$unsupported_project/flake.lock.invalid" \
|| fail "upgrade golden: unsupported identity lock fixture could not be prepared"
mv "$unsupported_project/flake.lock.invalid" "$unsupported_project/flake.lock"
before_flake=$(sha256sum "$unsupported_project/flake.nix")
before_lock=$(sha256sum "$unsupported_project/flake.lock")
before_project=$(sha256sum "$unsupported_project/nixfied.nix")
state="$unsupported_project/runtime-state"
for policy in plan forced; do
mode_args=(--plan)
[[ "$policy" != forced ]] || mode_args=(--force)
run_status=0
( NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$unsupported_project" --nixfied-url "$upgrade_new_url" "''${mode_args[@]}" \
>"$unsupported_project/stdout" 2>"$unsupported_project/stderr" ) \
|| run_status=$?
[ "$run_status" -eq 4 ] \
|| fail "upgrade golden: unsupported identity returned $run_status instead of 4"
grep -Fq 'sourcehut' "$unsupported_project/stderr" \
|| fail "upgrade golden: unsupported identity was not named"
grep -Fq 'candidate lock resolution failed' "$unsupported_project/stderr" \
|| fail "upgrade golden: unsupported identity omitted lock failure"
grep -Fq 'upgrade not applied; no project files were changed' "$unsupported_project/stderr" \
|| fail "upgrade golden: unsupported identity omitted no-mutation status"
! grep -Fq '"rev"' "$unsupported_project/stderr" \
|| fail "upgrade golden: unsupported identity fabricated a revision"
[ ! -s "$unsupported_project/stdout" ] \
|| fail "upgrade golden: unsupported identity emitted a partial documentation report"
assert_upgrade_unchanged "$unsupported_project" "$before_flake" "$before_lock" "$before_project" "unsupported identity"
[ ! -e "$state" ] || fail "upgrade golden: unsupported identity materialized runtime state"
done
rm -rf "$unsupported_project"
success_project=$(mktemp -d)
nix run "$upgrade_old_url#install" -- \
--root "$success_project" --project-id upgrade-golden-success --name upgrade-golden-success \
--nixfied-url "$upgrade_old_url" >/dev/null \
|| fail "upgrade golden: historical compatible install failed"
grep -Fq 'nixfied.surface.verbs = [ "smoke" ];' "$success_project/nixfied.nix" \
|| fail "upgrade golden: compatible fixture did not start from the list-form declaration"
${pkgs.gnused}/bin/sed -i \
'/^ nixfied.surface.verbs = \[ "smoke" \];$/d' \
"$success_project/nixfied.nix"
! grep -Fq 'nixfied.surface.verbs = [ "smoke" ];' "$success_project/nixfied.nix" \
|| fail "upgrade golden: compatible fixture retained the incompatible list declaration"
prepare_historical_project "$success_project"
nix flake lock "$success_project" >/dev/null \
|| fail "upgrade golden: historical compatible fixture lock failed"
before_flake=$(sha256sum "$success_project/flake.nix")
before_lock=$(sha256sum "$success_project/flake.lock")
before_project=$(sha256sum "$success_project/nixfied.nix")
state="$success_project/runtime-state"
nix run "$checkout#upgrade" -- \
--root "$success_project" --nixfied-url "$upgrade_new_url" --plan \
>"$success_project/plan.stdout" 2>"$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan failed"
assert_upgrade_golden "$success_project/plan.stdout" "compatible plan"
assert_evaluation_status "$success_project/plan.stderr" 'not run (--plan)'
assert_blank_after "$success_project/plan.stderr" \
'candidate manifest evaluation: not run (--plan)' 'compatible plan'
grep -Fq 'upgrade applied: no (--plan)' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted plan apply status"
grep -Fq 'plan: no project files changed' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted no-mutation status"
grep -Fq 'would change: flake.nix' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted flake.nix plan status"
grep -Fq 'would change: flake.lock (nixfied input)' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted flake.lock plan status"
grep -Fq 'preserved: nixfied.nix (project-owned)' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted project ownership"
grep -Fq 'post-upgrade validation: not run (--plan)' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted post-upgrade validation status"
grep -Fxq 'next:' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted next-step header"
grep -Fq 'rerun upgrade without --plan' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted apply next step"
grep -Fq 'the next invocation resolves upstream again' "$success_project/plan.stderr" \
|| fail "upgrade golden: plan implied exact candidate reuse"
grep -Fq -- 'remove --plan and add --force to skip only manifest evaluation; transaction safeguards still apply' "$success_project/plan.stderr" \
|| fail "upgrade golden: plan omitted explicit force choice and its limits"
grep -Fxq 'candidate selection: project input unless --nixfied-url is supplied; the supplying flake selects the upgrade tool' "$success_project/plan.stderr" \
|| fail "upgrade golden: plan confused tool selection with candidate selection"
grep -Fq 'redirect stdout to a file' "$success_project/plan.stderr" \
|| fail "upgrade golden: plan omitted documentation review guidance"
grep -Fxq 'validation after apply and any project wiring/declaration repairs:' "$success_project/plan.stderr" \
|| fail "upgrade golden: plan suggested validating the unchanged project"
grep -Fq ' nix build ' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted manifest build next step"
grep -Fq ' nix run ' "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted manifest-check next step"
grep -Fq " narHash: $upgrade_old_nar_hash" "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted old NAR identity"
grep -Fq " narHash: $upgrade_new_nar_hash" "$success_project/plan.stderr" \
|| fail "upgrade golden: compatible plan omitted candidate NAR identity"
assert_upgrade_unchanged "$success_project" "$before_flake" "$before_lock" "$before_project" "compatible plan"
[ ! -e "$state" ] || fail "upgrade golden: compatible plan materialized runtime state"
NIXFIED_STATE_DIR="$state" nix run "$checkout#upgrade" -- \
--root "$success_project" --nixfied-url "$upgrade_new_url" \
>"$success_project/apply.stdout" 2>"$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply failed"
assert_upgrade_golden "$success_project/apply.stdout" "compatible apply"
assert_evaluation_status "$success_project/apply.stderr" passed
assert_blank_after "$success_project/apply.stderr" \
'candidate manifest evaluation: passed' 'compatible apply'
grep -Fq 'upgrade applied: yes' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply omitted apply status"
grep -Fq 'changed: flake.nix' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply did not report flake.nix"
grep -Fq 'flake.lock (nixfied input)' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply did not report flake.lock"
grep -Fq 'preserved: nixfied.nix (project-owned)' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply did not report project ownership"
grep -Fq 'post-upgrade validation: not run' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply omitted post-upgrade validation status"
grep -Fxq 'next:' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply omitted next-step header"
grep -Fq ' nix build ' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply omitted manifest build next step"
grep -Fq ' nix run ' "$success_project/apply.stderr" \
|| fail "upgrade golden: compatible apply omitted manifest-check next step"
grep -Fq "$upgrade_new_url" "$success_project/flake.nix" \
|| fail "upgrade golden: compatible apply did not apply the pinned new URL"
jq -e --arg expected "$upgrade_new_nar_hash" \
'.nodes[.nodes[.root].inputs.nixfied].locked.narHash == $expected' \
"$success_project/flake.lock" >/dev/null \
|| fail "upgrade golden: compatible apply did not apply the pinned new lock"
[ "$before_project" = "$(sha256sum "$success_project/nixfied.nix")" ] \
|| fail "upgrade golden: compatible apply changed nixfied.nix"
[ ! -e "$state" ] \
|| fail "upgrade golden: compatible apply materialized runtime state"
nix build --no-link "$success_project#manifest" >/dev/null \
|| fail "upgrade golden: compatible applied manifest did not build"