Repository navigation
Expand file tree
/
Copy pathtasks.yaml
More file actions
5830 lines (5527 loc) · 348 KB
/
Copy pathtasks.yaml
File metadata and controls
5830 lines (5527 loc) · 348 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
# Redoubt — task board
#
# Source of truth for the port. Human narrative lives in ROADMAP.md; the working
# agreement for agents lives in AGENTS.md. Validate this file with:
#
# python3 docs/android/board.py --check
#
# Schema (every field required unless marked optional):
# id LW-M<milestone>-<nn>, stable forever, never renumbered
# title imperative, <= 70 chars
# milestone M0..M7
# depends_on task ids that must be DONE before this one starts
# owns paths in THIS repo (or the settings submodule) the task may write.
# Two tasks in the same wave must never share an owned path.
# shared_edit append-only, line-oriented files (the patch lists) that several
# tasks legitimately touch at once. Exempt from collision checking.
# Rule: only ever add or change the line for YOUR OWN patch.
# (optional, default [])
# tree_paths files inside the extracted Firefox tree that this task's patches
# touch. Also collision-checked — two patches editing the same tree
# file must be ordered, not parallel. (optional, default [])
# reads paths the task needs to read but must not modify (optional)
# what 2-5 sentences. Names concrete files, functions, line numbers.
# acceptance machine-checkable criteria, all must hold
# verify one shell command, or "manual: <what a human must do>"
# effort_h rough range in hours for one competent agent/engineer
# skills python|make|c++|js|kotlin|gradle|rust|shell|android|design|docs|ops
# agent_safe yes = a fresh agent with no prior context can do it from this file
# isolation none | worktree (worktree when the task mutates the source tree)
# risk one line: what goes wrong if this is done carelessly
# retired optional. Date and reason the task can no longer be done as
# written (the code it targets is gone upstream). A retired task
# is never offered by --ready and counts as done for the tasks
# that depend on it. The id stays; the history stays readable.
#
# NOTE ON THE settings SUBMODULE
# settings/ is a git submodule (github.com/CPlusPlus17/Redoubt/settings). Tasks that own
# paths under settings/ produce a PR in THAT repo plus a submodule bump here.
# They are marked with `submodule: settings`.
meta:
project: Redoubt
strategy: full parity — Gecko built from source, one shared patch set with desktop
decided: 2026-08-14
track: esr153 for Android while desktop stays on release (see LW-M0-07)
forge: github.com/CPlusPlus17/Redoubt (public; self-hosted Actions runner
'redoubt-fedora', label librewolf-android; workflows in .github/workflows/.
Corrected 2026-09-06 — this said codeberg.org, and .forgejo/ is dead here.
Upstream is read-only from github.com/mozilla-firefox/firefox)
patch_scope_snapshot: 22 common / 23 desktop / 8 straddlers of 53 patch files
(heuristic — LW-M1-01 produces the authoritative split)
tasks:
# ---------------------------------------------------------------- M0
# Foundations. Everything here is agent-safe, needs no Gecko build, and
# (apart from the stated deps) runs in parallel. This is the widest wave.
- id: LW-M0-01
title: Add a TARGETS variable to the Makefile and thread it to the patcher
milestone: M0
depends_on: []
owns: [Makefile]
reads: [scripts/librewolf-patches.py]
what: |
Makefile:136-140 builds $(lw_source_dir) by calling
`python3 scripts/librewolf-patches.py $(version) $(release)` with no notion
of a platform. Add `TARGETS ?= desktop` and pass `--targets=$(TARGETS)`.
Add the variable to the $(lw_source_dir) prerequisite list so changing it
forces a re-extract. Do not change patcher behaviour in this task — only
the plumbing, so `make dir` with no TARGETS is byte-identical to today.
acceptance:
- "`make dir` with no arguments produces the same tree as before this change"
- "`make -n dir TARGETS=desktop,android` shows the patcher invoked with --targets=desktop,android (the runtime half is gated on LW-M0-02, which adds the flag to the patcher)"
- "the TARGETS value participates in the $(lw_source_dir) prerequisites — directly or via a TARGETS-derived stamp — so changing it forces a re-extract"
verify: "make -n dir TARGETS=desktop,android | grep -- '--targets=desktop,android'"
effort_h: "1-2"
skills: [make]
agent_safe: yes
isolation: none
risk: Silently changing the default target breaks every existing desktop builder.
- id: LW-M0-02
title: Split patches.txt into common/desktop/android lists
milestone: M0
depends_on: [LW-M0-01, LW-M0-05]
owns:
- assets/patches/common.txt
- assets/patches/desktop.txt
- assets/patches/android.txt
- assets/patches.txt
- scripts/librewolf-patches.py
- Makefile
what: |
Add `--targets` to scripts/librewolf-patches.py (optparse block at :22-25).
LW-M0-01 left a conditional in the Makefile that suppresses the flag when
TARGETS is the default, precisely because the patcher rejects it today —
once this task lands, that conditional can be removed and the flag passed
unconditionally. Do that, and delete the comment that explains it.
Replace the single read of ../assets/patches.txt at :115-117 with: always
apply common.txt, then apply desktop.txt and/or android.txt per --targets.
Keep assets/patches.txt as a generated compatibility shim (common+desktop
concatenated) until LW-M7-01 retires it. Seed the three lists from the
current 51 entries using the heuristic in PATCH-SCOPE.md; LW-M1-01 corrects
it. The separately-applied patches at :121 (xmas) and :145 (pref-pane) stay
where they are for now — LW-M1-09 and LW-M1-10 move them.
Two pre-existing defects found by LW-M0-04 land in your lap because you own
this file. `--no-execute` is NOT side-effect-free today: the version.txt
rewrite (`:224-226` after the M0-04 change) runs unconditionally and really
writes `browser/config/version.txt`, and `open('../assets/patches.txt')` is
read relative to cwd even though `enter_srcdir()` did not chdir under -n. Fix
both — a dry run that mutates the tree makes this task's own verify command
impossible to write. Also gate the `srcdir + '/configure.py'` existence check
at the bottom of the script on `no_execute`.
Finally, in the Makefile (which you now own): add `assets/l10n-pin.txt` to the
`$(lw_source_dir)` prerequisite list so bumping the pin re-triggers patching,
and drop the `ifneq ($(TARGETS),desktop)` conditional around `patcher_cmd`
that LW-M0-01 left as a bridge.
Also stale in your file: the comment at librewolf-patches.py:124-127 still
cites ".rej-only detection" as a live reason for keeping the OpenAI deletions
in Python. LW-M0-11 fixed that detector. The size argument still stands —
only the detector claim is outdated. Correct the comment, keep the code.
Two more Makefile items, both found by LW-M0-07. First, a pre-existing bug:
`ff_source_tarball` is assigned TWICE (once with `$(FF_BETA_SUFFIX)`, once
without); the second assignment wins, so FF_BETA_SUFFIX has no effect on the
tarball name and `make test-beta` cannot work. Second, `targets_stamp` is
version-agnostic and its recipe does `rm -f librewolf-targets-*`, so now that
Android pins its own version, every TARGETS switch re-extracts a tree that
was already correct — about 10GB of churn. TRACK.md has a two-line fix; it
renames the stamp, so it must land with a note that the first `make dir`
after it re-extracts once.
(Non-issue, recorded so nobody re-investigates: assets/mozconfig and
assets/mozconfig.new look like duplicates but mozconfig is a SYMLINK to
mozconfig.new. The patcher copies mozconfig.new and the Makefile lists
assets/mozconfig as the prerequisite; make follows the link, so this is
correct as-is. Leave it alone.)
acceptance:
- "common.txt + desktop.txt == the current patches.txt set (as a set, order preserved within each)"
- "`make dir` (default targets) applies exactly the same patches in the same order as before"
- "--targets=android applies common.txt + android.txt and no browser/-only patch"
- "patcher exits non-zero on an unknown target name"
- "`--no-execute` writes nothing to disk and works from any cwd"
- "assets/l10n-pin.txt is a prerequisite of $(lw_source_dir)"
verify: "cd /tmp && python3 $OLDPWD/scripts/librewolf-patches.py --no-execute --targets=android 153.0.4 1 | grep -c 'patch -p1'"
effort_h: "3-5"
skills: [python, shell]
agent_safe: yes
isolation: none
risk: Order within the common list is load-bearing — see AGENTS.md "patch ordering".
- id: LW-M0-03
title: Write assets/mozconfig.android carrying the desktop hardening flags
milestone: M0
depends_on: []
owns: [assets/mozconfig.android]
reads: [assets/mozconfig, assets/mozconfig.new]
what: |
assets/mozconfig.new is the desktop config. Produce the Android equivalent:
`ac_add_options --enable-application=mobile/android`, `--target=aarch64-linux-android`,
Android NDK/SDK paths, and every hardening flag the desktop config carries —
--enable-hardening, --enable-stl-hardening, --enable-replace-malloc,
--enable-jemalloc, and the -ftrivial-auto-var-init=zero -fwrapv CFLAGS. Also
set --enable-appservices-in-tree (needed by LW-M2-05) and the LibreWolf
branding/official-build options the desktop config uses. Document every flag
you had to DROP and why, as comments in the file — that list is the input to
LW-M5-03.
acceptance:
- "file exists and every hardening option present in assets/mozconfig.new is either present or commented with a reason"
- "no reference to browser/ or desktop-only branding paths"
- "--enable-appservices-in-tree present"
verify: "python3 docs/android/board.py --diff-mozconfig"
effort_h: "3-6"
skills: [shell, android]
agent_safe: yes
isolation: none
risk: A silently dropped hardening flag is the exact parity loss this project exists to avoid.
- id: LW-M0-04
title: Pin the l10n fetch to a commit and verify its hash
milestone: M0
depends_on: []
owns: [scripts/librewolf-patches.py, assets/l10n-pin.txt]
what: |
scripts/librewolf-patches.py:161-165 downloads
codeload.github.com/mozilla-l10n/firefox-l10n/zip/refs/heads/main — an
unpinned, unverified moving target fetched into every release build. Replace
the ref with a commit sha read from a new assets/l10n-pin.txt, and verify the
downloaded zip against a sha256 recorded in the same file. Fail the build on
mismatch. This fixes desktop too and should land on desktop first.
acceptance:
- "no refs/heads/ URL remains in the script"
- "a corrupted download makes the patcher exit non-zero"
- "assets/l10n-pin.txt records both the commit sha and the archive sha256"
verify: "! grep -r 'refs/heads/main' scripts/librewolf-patches.py"
effort_h: "2-4"
skills: [python, shell]
agent_safe: yes
isolation: none
risk: A wrong pin breaks localisation silently — en-US still works, everything else regresses.
- id: LW-M0-05
title: Move the out-of-patch OpenAI deletions into the patch set
milestone: M0
depends_on: [LW-M0-04]
owns: [scripts/librewolf-patches.py, patches/remove-openai.patch]
reads: [patches/remove-openai.patch]
what: |
scripts/librewolf-patches.py:97-98 runs two `rm -rf` against
toolkit/components/ml/ outside any patch, so `make check-patchfail` cannot
see them and an upstream rename fails open (files silently stay). Fold both
deletions into patches/remove-openai.patch (which already exists and covers
the rest), or, if a patch cannot express a directory deletion cleanly, keep
the rm but make it assert the paths existed. Landmine L4 in AGENTS.md.
acceptance:
- "the two bare `rm -vf`/`rm -vrf` calls at :97-98 are gone or now fail loudly on a missing path"
- "a fresh `make dir` still leaves no toolkit/components/ml/vendor/openai"
- "`make check-patchfail` covers the deletion"
verify: "! grep -n 'rm -vrf toolkit/components/ml/vendor/openai' scripts/librewolf-patches.py"
effort_h: "1-3"
skills: [python]
agent_safe: yes
isolation: none
risk: Deleting more than upstream ships breaks the ml component's moz.build.
- id: LW-M0-11
title: Make check-patchfail respect patch's exit code
milestone: M0
depends_on: [LW-M0-05]
owns: [scripts/check-patchfail.sh, patches/remove-openai.patch]
what: |
FOUND BY LW-M0-05, WITH A WORKING COUNTER-DEMO. check-patchfail.sh:32-47
discards `patch`'s exit code entirely and detects failure only by
`grep -n 'rej$'` on the output. When a hunk's target file is missing, patch
prints "Skipping patch. / 1 out of 1 hunk ignored", writes NO .rej file, and
exits 1 — and check-patchfail reports success. stderr is not captured into
patch.tmp either. Any pure-deletion or pure-addition patch is therefore
silently fail-open, which is landmine L4's shape in the tool that is supposed
to catch L4. Make the exit code authoritative, capture stderr, and keep the
.rej scan as a second signal rather than the only one.
While in the area: remove-openai.patch misses two NIGHTLY_BUILD-guarded
references to vendor/openai/dist/openai-dev.mjs (jar.mn:43,
license.html:1535). Harmless on release, breaks packaging on a nightly-channel
build after the rm. Fix or document. Note jar.mn currently applies with
fuzz 2 — pre-existing, do not paper over it.
acceptance:
- "a patch whose target file is missing makes check-patchfail report failure"
- "patch's stderr appears in the output it scans"
- "the existing .rej detection still fires (do not replace one narrow signal with another)"
- "a run against the pristine 153.0.4 tree still reports success"
verify: "./scripts/check-patchfail.sh"
effort_h: "2-4"
skills: [shell]
agent_safe: yes
isolation: worktree
risk: This tool is the safety net for every other patch task; while it fails open, every green result it gives is unreliable.
- id: LW-M0-12
title: Add container-engine indirection and Android image targets
milestone: M0
depends_on: [LW-M0-02, LW-M0-06]
owns: [Makefile]
what: |
FOUND BY LW-M0-06. The docker-build-image / docker-run-build-job /
docker-remove-image targets call `docker` directly, and docker is not
installed on every maintainer machine — podman is what LW-M0-06 actually
built the Android image with. Add a `CONTAINER_ENGINE ?= docker` indirection
so podman works without editing the Makefile, and add the Android
counterparts (android-build-image, android-run-build-job,
android-remove-image) pointing at assets/Dockerfile.android.
Note docker-build-image pipes the Dockerfile on stdin with no build context;
assets/Dockerfile.android COPYs nothing, so it works either way. Keep that
property or state that you changed it.
acceptance:
- "`make docker-build-image CONTAINER_ENGINE=podman` works"
- "the default with no CONTAINER_ENGINE set is unchanged for existing docker users"
- "the android image targets build assets/Dockerfile.android"
verify: "make -n android-build-image CONTAINER_ENGINE=podman"
effort_h: "1-2"
skills: [make, ops]
agent_safe: yes
isolation: none
risk: Low, but leaving it undone means every podman-using maintainer edits the Makefile locally and eventually commits it.
- id: LW-M0-14
title: Derive ff_source_dir from the tarball, not from the version string
milestone: M0
depends_on: [LW-M0-02, LW-M0-07, LW-M0-11, LW-M0-12]
owns: [Makefile, scripts/check-patchfail.sh]
what: |
BLOCKER, found by LW-M0-09 and independently confirmed: `make dir
TARGETS=android` cannot succeed. The ESR tarball
firefox-153.0esr.source.tar.xz extracts to `firefox-153.0/`, but the Makefile
computes `ff_source_dir := firefox-$(version)` = `firefox-153.0esr`, so the
recipe runs `mv firefox-153.0esr librewolf-153.0esr-1` and fails — AFTER a
766MB download and a ~10GB extract.
Derive the extracted directory from the tarball itself rather than assuming
it matches the version string, or strip the `esr` suffix. Deriving is better:
it also survives whatever Mozilla does next.
DO NOT USE `tar tf | head -1`. An earlier version of this task said the
tarball's first member is `firefox-153.0/CLOBBER`. It is not — the first
member is `./`, on both the ESR and the release tarball, so
`tar tf | head -1 | cut -d/ -f1` yields `.` and that value is used as an
`rm -rf` operand. Strip a leading `./` and explicitly reject "", "." and ".."
before using the result. Do the derivation in the RECIPE shell, not with
`$(shell ...)`: a parse-time call runs on every `make help` and `make -n`,
and on a fresh checkout it runs before the tarball exists and evaluates to
empty.
scripts/check-patchfail.sh:50 has the identical assumption
(`cd firefox-$(cat ../version)`) — fix both, and note LW-M1-11 will also touch
that file.
Keep the desktop path byte-identical: for a release tarball the derived name
already equals firefox-$(version).
acceptance:
- "`make dir TARGETS=android` gets past the mv and into the patcher"
- "`make -n dir` for desktop is unchanged"
- "check-patchfail works against an esr tarball"
- "the directory name is derived or normalised, not special-cased for the string 'esr'"
verify: "make -n dir TARGETS=android"
effort_h: "2-4"
skills: [make, shell]
agent_safe: yes
isolation: none
risk: Every Android CI run and every Android build is red until this lands, and the failure costs a 766MB download each time.
- id: LW-M0-15
title: Add the four missing host toolchains to the Android image
milestone: M0
depends_on: [LW-M0-06, LW-M2-01]
owns: [assets/Dockerfile.android]
what: |
FOUND BY LW-M2-01, which got a green build only by installing these by hand.
The image has the Android toolchains right but omits four host tools that are
hard configure stops, so a fresh container cannot build without manual setup.
All four go in $MOZBUILD_STATE_PATH, where bootstrap_path()
(bootstrap.configure:301-306) finds them even with --enable-bootstrap at its
default:
1. clang — symlink $MOZBUILD_STATE_PATH/clang to the NDK's
prebuilt/linux-x86_64 (toolchain.configure:760 is
bootstrap_search_path("clang/bin")). Without it configure picks
/usr/bin/gcc and dies on a CPU mismatch. NDK clang 21.0.0 then serves
target, host, assembler and wasm.
2. Node.js 22.16.0 — required by node.configure. The jammy package is
12.22.9 and NODE_MIN_VERSION is 12.22.12, so the distro package is
unusable, not merely old.
3. cbindgen 0.29.4 — `cargo install --root $MOZBUILD_STATE_PATH/cbindgen`
plus a symlink so the binary sits at cbindgen/cbindgen.
4. WASI sysroot + wasm32 compiler-rt — RLBox is on by default for all
little-endian targets (toolkit/moz.configure:2829-2833), so
aarch64-android needs it. Fetch Mozilla's own artifacts
(`mach artifact toolchain --from-build sysroot-wasm32-wasi` and
`wasm32-wasi-compiler-rt-21`) and copy the builtins into the NDK as
lib/clang/21/lib/wasm32-unknown-wasi/libclang_rt.builtins.a — note the
rename from the artifact's flat libclang_rt.builtins-wasm32.a.
DO NOT take configure's suggestion of --without-wasm-sandboxed-libraries.
LW-M2-01 explicitly refused it: turning off RLBox to make configure quiet is
a silent parity loss, and it is exactly the failure this project exists to
avoid.
Do NOT add python3.10-venv. `python3 -m venv` is broken in the image
(ensurepip missing on jammy) and it does not matter — mach uses the vendored
third_party/python/virtualenv.
acceptance:
- "a fresh container from the rebuilt image reaches `./mach build` with no manual toolchain setup"
- "RLBox stays enabled — --without-wasm-sandboxed-libraries appears nowhere"
- "`python3 docs/android/board.py --diff-mozconfig` still passes"
verify: "podman build -f assets/Dockerfile.android -t librewolf-android-build ."
effort_h: "3-6"
skills: [ops, android, shell]
agent_safe: yes
isolation: none
risk: Leaving these out means every builder and the CI job rediscovers four configure stops by hand.
- id: LW-M0-16
title: Add nasm to the Android image and correct the BUILD.md claim
milestone: M0
depends_on: [LW-M0-15, LW-M2-03]
owns: [assets/Dockerfile.android, docs/android/BUILD.md]
what: |
FOUND BY THE SKEPTICAL VERIFICATION OF LW-M2-03, and confirmed independently:
`make android-aar` cannot run from a clean checkout. The x86_64 ABI needs
nasm; `grep -ci nasm assets/Dockerfile.android` is 0, and the built image has
neither nasm on PATH nor $MOZBUILD_STATE_PATH/nasm. The three-ABI fat AAR was
only produced with an ad-hoc `librewolf-android-build-nasm` image made by
podman commit on this host — it exists in NO repo file, so nobody else can
reproduce the deliverable.
Worse, the requirement is currently recorded only in a comment inside
scripts/android-fat-aar.sh, while docs/android/BUILD.md:415 still states
"**No `nasm`/`yasm`** is needed: they are x86-only" as an unqualified bullet —
in the very section that hands off to the image owner with "Suggested layers".
That was true for the aarch64-only build and is false for the fat AAR.
Whoever maintains the image will read BUILD.md, not another task's script.
Add nasm to assets/Dockerfile.android following the established pattern
(pinned, with the tree file:line the pin came from), rebuild, and correct
BUILD.md. Then DELETE the ad-hoc image so nobody depends on it again.
acceptance:
- "assets/Dockerfile.android installs nasm, pinned, with a source comment"
- "the rebuilt librewolf-android-build image passes scripts/android-fat-aar.sh's x86_64 preflight"
- "BUILD.md no longer claims nasm is unnecessary, and says which ABI needs it"
- "no ad-hoc podman-commit image is required to reproduce make android-aar"
verify: "podman run --rm librewolf-android-build bash -lc 'command -v nasm'"
effort_h: "1-3"
skills: [ops, android]
agent_safe: yes
isolation: none
risk: The headline M2 deliverable is currently reproducible only on one machine, and the doc actively misleads whoever would fix it.
- id: LW-M0-13
title: Teach enable-patch and disable-patch the split lists
milestone: M0
depends_on: [LW-M0-02]
owns: [scripts/enable-patch.sh, scripts/disable-patch.sh]
what: |
FOUND BY LW-M0-02. Both scripts edit only assets/patches.txt, which after the
split is a generated compatibility shim that no longer drives any build. So a
maintainer who runs `enable-patch.sh` gets a patch that silently never reaches
a build — fail-open, exactly landmine L4's shape. LW-M0-02 added a non-fatal
drift warning in the patcher that surfaces it; this task fixes it. The scripts
must ask which list a patch belongs to (or infer it and say what it inferred),
edit that list, and regenerate the shim. Note they currently `sort` the shim,
which is why LW-M0-02's drift check compares sets rather than sequences.
acceptance:
- "enable-patch.sh adds to the correct target list and the patch actually applies on the next build"
- "disable-patch.sh removes from whichever list holds the patch"
- "the shim stays consistent, and the patcher's drift warning stays silent after either script runs"
verify: "./scripts/enable-patch.sh --help && ! python3 scripts/librewolf-patches.py --no-execute 153.0.4 1 2>&1 | grep -qi 'out of sync'"
effort_h: "2-3"
skills: [shell]
agent_safe: yes
isolation: none
risk: A maintainer trusting these scripts ships a build missing the patch they just enabled, with no error anywhere.
- id: LW-M0-06
title: Build a pinned Android toolchain image (assets/Dockerfile.android)
milestone: M0
depends_on: []
owns: [assets/Dockerfile.android]
reads: [assets/Dockerfile]
what: |
Mirror assets/Dockerfile for Android: JDK, Android SDK + build-tools, NDK,
Rust with the four Android targets (aarch64/armv7/x86_64/i686-linux-android),
and the mach bootstrap deps. Pin every version explicitly and record the
tree file:line each pin came from, so the next rebase can re-check.
READ THE PINS FROM (verified — mobile/android/gradle.py holds NO version
constants, it is a 52-line gradle invocation helper):
python/mozboot/mozboot/android.py, python/mozboot/mozboot/android-packages.txt,
build/moz.configure/android-sdk.configure, build/moz.configure/android-ndk.configure,
gradle/libs.versions.toml, mobile/android/android-components/.config.yml, and
taskcluster/kinds/toolchain/rust.yml (linux64-rust-android is the version
Mozilla's Android builds actually use — Cargo.toml's floor is only a floor).
Two traps: the JDK must physically live at $MOZBUILD_STATE_PATH/jdk/... because
java.configure:29-34 ignores JAVA_HOME; and the `emulator` SDK package is NOT
optional despite looking test-only, because android-sdk.configure:376 runs
check_android_tools("emulator") unconditionally for mobile/android and dies
without it.
acceptance:
- "every pinned version is traceable to a tree file:line recorded in a comment"
- "`rustup target list --installed` shows all four Android targets, verified by running it inside the built image"
- "sdkmanager --licenses runs non-interactively — the build never blocks on a prompt"
- "the NDK revision is asserted after download, not merely requested"
verify: "podman build -f assets/Dockerfile.android -t librewolf-android-build ."
note: >-
Full network hermeticity is deliberately NOT required here — the house style
in assets/Dockerfile hits Ubuntu archives, sh.rustup.rs and codeberg. A
hermetic image is a separate piece of work if it is ever wanted.
effort_h: "4-8"
skills: [ops, android, shell]
agent_safe: yes
isolation: none
risk: An NDK mismatch produces a tree that configures fine and fails deep in the link.
- id: LW-M0-07
title: Decide and document the Android release track (esr153)
milestone: M0
depends_on: [LW-M0-01]
owns: [docs/android/TRACK.md, version.android, release.android, Makefile]
reads: [version, release]
what: |
DONE — see TRACK.md for the evidence. Decision upheld (Android on esr153,
desktop on release) but three of the four figures this task originally
carried were wrong: the current tarball is release 153.0.4, NOT esr153
(`MOZ_ESR` is unset per init.configure:1151), so adoption costs a second
source tree rather than nothing; ESR ships ~26 dots a year, not ~12; and the
real argument is hard rebases falling 26/yr to 1/yr. Recorded costs: median
14-day / max 21-day security lag, and NO Firefox for Android ESR exists, so
Android-only advisories must be watched and backported by hand.
acceptance:
- "TRACK.md states the decision, the yearly rebase cost of each option, and the reversal cost"
- "version.android/release.android exist and are read by the android make targets"
- "the desktop version/release files are untouched"
verify: "manual: maintainer sign-off recorded in TRACK.md"
effort_h: "2-4"
skills: [docs, make]
agent_safe: yes
isolation: none
risk: Coupling Android to the desktop version file forces a lockstep rebase cadence nobody has staff for.
- id: LW-M0-08
title: Write a file-level patch-scope linter
milestone: M0
depends_on: [LW-M0-02]
owns: [scripts/lint-patch-scope.py]
reads: [assets/patches/common.txt, assets/patches/desktop.txt, assets/patches/android.txt]
what: |
Parse the `--- a/x +++ b/x` headers of every patch and assert that patches in
common.txt touch no desktop-only file, desktop.txt touches no mobile/ file,
and android.txt touches no browser/ file. The rule must be FILE-level, not
prefix-level: ui-patches/neterror.patch looks like pure toolkit/ but edits
toolkit/themes/shared/desktop-jar.inc.mn. Conversely devtools/ must be
ALLOWED in common — devtools/moz.build:11-16 ships server/ and shared/ on
Android. Ship the allow/deny lists as data at the top of the file with a
comment per entry.
acceptance:
- "linter flags a common patch that touches browser/ or *desktop*.inc.mn"
- "linter does NOT flag a common patch that touches devtools/server/"
- "exits 0 on the current tree once LW-M1-01 has landed, non-zero before"
verify: "python3 scripts/lint-patch-scope.py"
effort_h: "3-5"
skills: [python]
agent_safe: yes
isolation: none
risk: A prefix-based rule passes on neterror.patch and lets desktop-only code into the Android build.
- id: LW-M0-09
title: Add the android-test CI workflow skeleton
milestone: M0
depends_on: [LW-M0-01, LW-M0-06]
owns: [.forgejo/workflows/android-test.yaml]
reads: [.forgejo/workflows/source-test.yaml, .forgejo/workflows/source-release.yaml]
what: |
Copy the shape of source-test.yaml onto Android: run on the epsilon runner,
run `make dir TARGETS=android` (NOT `common,android` — `common` is not a
target and the patcher rejects it), then the scope and order checks. Do NOT
attempt a Gecko build in this task — the job must stay under the runner's
timeout. LW-M2-08 adds the real build job once caching exists.
Two steps must be ADVISORY, not blocking, or the job is red on day one for
reasons unrelated to Android: lint-patch-scope.py is specified to exit
non-zero until LW-M1-01 reclassifies, and check-patchfail.sh is target-blind
until LW-M1-11 (note LW-M1-11 depends on THIS task, so it cannot be a
prerequisite). `make dir TARGETS=android` is the real gate — the patcher
exits 1 on the first failing patch on its own.
acceptance:
- "workflow runs on push and PR"
- "`make dir TARGETS=android` is the blocking gate and fails the job when a patch fails"
- "steps that are known-red until a later task are advisory, with a comment naming the task that makes them blocking"
- "no signing secrets referenced"
verify: "manual: open a PR and confirm the job runs on the epsilon runner"
effort_h: "2-4"
skills: [ops, shell]
agent_safe: yes
isolation: none
risk: A build step here blows the runner budget and the job gets disabled instead of fixed.
- id: LW-M0-10
title: Create patches/android/ and its README
milestone: M0
depends_on: [LW-M0-02]
owns: [patches/android/README.md, patches/android/.gitkeep]
what: |
All Android-only patches live in patches/android/. Write the README that
states the naming convention (one concern per patch, kebab-case, no version
in the name), that every patch must carry a header comment naming the
upstream file and why LibreWolf diverges, and that a patch which also needs
to touch shared Gecko code belongs in common/ instead with the Android half
gated at runtime, not duplicated.
acceptance:
- "directory exists and is referenced by assets/patches/android.txt"
- "README states naming, header-comment, and no-duplication rules"
verify: "test -f patches/android/README.md"
effort_h: "1"
skills: [docs]
agent_safe: yes
isolation: none
risk: Without a stated convention the Android patch dir turns into the desktop one's mess twice as fast.
# ---------------------------------------------------------------- M1
# Patch-set surgery. LW-M1-01 gates everything; the eight straddler tasks are
# then fully parallel because each owns disjoint patch files.
- id: LW-M1-01
title: Produce the authoritative common/desktop/android patch split
milestone: M1
depends_on: [LW-M0-02, LW-M0-08]
owns:
- assets/patches/common.txt
- assets/patches/desktop.txt
- assets/patches/android.txt
- docs/android/PATCH-SCOPE.md
reads: [patches/]
what: |
The snapshot in PATCH-SCOPE.md (22 common / 23 desktop / 8 straddlers) comes
from a path heuristic and is a starting point, not an answer. Open every one
of the 53 patch files and classify it by what it actually does, not by which
directory it edits. Record a one-line justification per patch in
PATCH-SCOPE.md and mark each row reviewed. Straddlers stay unassigned here —
they are handed to LW-M1-02..09.
acceptance:
- "all 53 patches classified, every row carries a justification and a reviewer"
- "scripts/lint-patch-scope.py exits 0"
- "counts in PATCH-SCOPE.md match the three .txt files"
verify: "python3 scripts/lint-patch-scope.py && python3 docs/android/board.py --check-scope && python3 docs/android/board.py --check"
effort_h: "6-10"
skills: [python, c++, js]
agent_safe: yes
isolation: none
risk: A patch misfiled as common lands desktop assumptions in the Android build and shows up as a runtime crash months later.
- id: LW-M1-02
title: Split disable-data-reporting-at-compile-time into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/disable-data-reporting-at-compile-time.patch
- patches/disable-data-reporting-common.patch
- patches/disable-data-reporting-desktop.patch
- patches/android/disable-data-reporting-android.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- browser/moz.configure
- browser/components/tabbrowser/content/tabbrowser.js
- python/mach/mach/telemetry.py
- python/sites/mach.txt
- toolkit/components/glean/src/init/mod.rs
- toolkit/components/telemetry/core/Telemetry.cpp
- toolkit/library/rust/gkrust-features.mozbuild
what: |
The common half (mach telemetry, Glean init, Telemetry.cpp, the gkrust
feature list) is exactly what Android needs; the browser/moz.configure and
tabbrowser.js hunks are desktop-only. Split into two patches that apply
cleanly in sequence and produce a byte-identical desktop tree. Note that
browser/moz.configure carries the MOZ_DATA_REPORTING off switch — find the
mobile/android equivalent and record whether it needs the same treatment
(input to LW-M4-01).
acceptance:
- "desktop tree after both patches is byte-identical to the tree after the original patch"
- "common patch applies to a tree with mobile/android as the only front end"
- "a note in the common patch header points at the mobile/android data-reporting switch"
verify: "./scripts/check-patchfail.sh"
effort_h: "2-4"
skills: [c++, rust, python]
agent_safe: yes
isolation: worktree
risk: Dropping a gkrust feature hunk silently re-enables Glean in the Rust build.
- id: LW-M1-03
title: Split eme-permission into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/eme-permission.patch
- patches/eme-permission-common.patch
- patches/eme-permission-desktop.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- dom/media/eme/MediaKeySystemAccessManager.cpp
- browser/components/permissions/ContentPermissionPrompt.sys.mjs
- browser/modules/EMEPermissionPrompt.sys.mjs
what: |
MediaKeySystemAccessManager.cpp is the enforcement point and is common; the
two browser/ files are the desktop prompt UI. Split them.
RESOLVED BY LW-M1-03, and the premise this task was written on was WRONG.
"Android has no equivalent surface yet" is false: Fenix has implemented this
exact permission type end to end all along — GeckoViewPermission.sys.mjs:119-219
is type-agnostic and forwards any perm.type to the embedder (the structural
difference from webgl-permission, whose observers exist only under browser/),
GeckoSession.java carries PERMISSION_MEDIA_KEY_SYSTEM_ACCESS across JNI, and
Fenix has both a doorhanger and a settings entry (PhoneFeature.kt:38,
SitePermissionsRules.kt:92-94, default ASK_TO_ALLOW). No Android patch needed.
The one deliberate silent deny is Clear Key, and it is identical on both
platforms — the pref is read as
Preferences::GetBool("librewolf.eme.gmp-clearkey.enabled", false), so the
built-in fallback already equals LibreWolf's default. That is exactly why
webgl-permission needed an Android patch and this does not: webgl's shared
default was the permissive-looking true.
The real remaining gap is narrower: there is no Android surface for the
LibreWolf-specific Clear Key denial (desktop's learn-more link to
librewolf.eme.warning.infoURL). That is a UI follow-up, not a scope question.
acceptance:
- "desktop tree byte-identical after the two patches"
- "common patch applies with no browser/ present"
- "chosen Android behaviour recorded in the common patch header"
verify: "./scripts/check-patchfail.sh"
effort_h: "2-4"
skills: [c++, js]
agent_safe: yes
isolation: worktree
risk: Enforcement without any user-visible signal reads as a broken video player.
- id: LW-M1-04
title: Split moz-official into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/moz-official.patch
- patches/moz-official-common.patch
- patches/moz-official-desktop.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- dom/media/gmp/ChromiumCDMAdapter.cpp
- media/gmp-clearkey/0.1/gmp-clearkey.cpp
- browser/base/content/browser-init.js
- browser/base/content/browser-main.js
- browser/base/jar.mn
what: |
The two media/ files (clearkey, CDM adapter) are common and matter on
Android; the three browser/base files are desktop chrome. Split, keeping the
desktop tree identical. Check whether mobile/android has its own equivalent
of the browser-init hunk — if it does, that becomes an android patch, and if
it does not, say so in the header rather than leaving it ambiguous.
acceptance:
- "desktop tree byte-identical after the two patches"
- "common patch applies with no browser/ present"
- "header records whether an Android equivalent of the browser-init hunk is needed"
verify: "./scripts/check-patchfail.sh"
effort_h: "2-3"
skills: [c++, js]
agent_safe: yes
isolation: worktree
risk: The clearkey hunks interact with librewolf.eme.gmp-clearkey.enabled — losing them changes DRM behaviour.
- id: LW-M1-05
title: Split msix into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/msix.patch
- patches/msix-common.patch
- patches/msix-desktop.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- python/mozbuild/mozbuild/mach_commands.py
- python/mozbuild/mozbuild/repackaging/msix.py
- browser/installer/windows/msix/AppxManifest.xml.in
what: |
msix.patch looks like the most obviously desktop-only patch in the tree and
is not: it edits python/mozbuild/mozbuild/mach_commands.py, a file the
Android build traverses. Either split out the mach_commands hunk as common,
or confirm the hunk is inert for Android and mark the whole patch desktop
with that finding written in PATCH-SCOPE.md. Do not guess — read the hunk
and check whether the code path is reachable from `mach build` on Android.
acceptance:
- "a written finding: either the split exists, or the hunk is proven inert with the reasoning recorded"
- "desktop tree byte-identical either way"
verify: "./scripts/check-patchfail.sh"
effort_h: "1-3"
skills: [python]
agent_safe: yes
isolation: worktree
risk: This is the straddler most likely to be dismissed on sight; dismissing it wrongly breaks `mach` on Android.
- id: LW-M1-06
title: Split remove-pingsender into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/remove-pingsender.patch
- patches/remove-pingsender-common.patch
- patches/remove-pingsender-desktop.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- toolkit/components/telemetry/moz.build
- toolkit/components/telemetry/app/TelemetrySend.sys.mjs
- python/mozbuild/mozbuild/artifacts.py
- browser/installer/package-manifest.in
- browser/installer/windows/nsis/shared.nsh
- browser/app/macbuild/Contents/MacOS-files.in
what: |
The toolkit/ and python/ hunks are common; the three packaging files are
desktop installers. Split them. The Android packaging equivalent
(mobile/android's package manifest / Gradle assets) may also need pingsender
excluded — check and file it as an android patch under patches/android/ if
so.
acceptance:
- "desktop tree byte-identical after the two patches"
- "common patch applies with no browser/ present"
- "a stated answer on whether Android packaging ships pingsender"
verify: "./scripts/check-patchfail.sh"
effort_h: "2-4"
skills: [js, python]
agent_safe: yes
isolation: worktree
risk: Removing the moz.build entry without the send-path hunk leaves a build referencing a binary that no longer exists.
- id: LW-M1-07
title: Split ui-patches/neterror into common and desktop
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/ui-patches/neterror.patch
- patches/ui-patches/neterror-common.patch
- patches/ui-patches/neterror-desktop.patch
- patches/android/neterror-jar.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- toolkit/content/errors/net-error-illustrations.mjs
- toolkit/content/net-error-card.mjs
- toolkit/themes/shared/aboutNetError.css
- toolkit/themes/shared/illustrations/warning.svg
- toolkit/themes/shared/desktop-jar.inc.mn
what: |
This is the patch that breaks prefix-based scope rules: everything is under
toolkit/ but toolkit/themes/shared/desktop-jar.inc.mn is desktop-only. Split
the jar manifest hunk into the desktop patch and find the Android jar
manifest that must carry the same additions (mobile/android/themes or the
geckoview jar.mn) so the illustration and CSS actually ship on Android.
acceptance:
- "desktop tree byte-identical after the two patches"
- "common patch touches no *desktop*.inc.mn"
- "the Android jar manifest entry exists, or a written finding says none is needed"
verify: "python3 scripts/lint-patch-scope.py"
effort_h: "2-4"
skills: [js, shell]
agent_safe: yes
isolation: worktree
risk: Shipping the .mjs without the jar entry gives a blank error page on Android with no console error.
- id: LW-M1-08
title: Split webgl-permission into common and desktop — the L1 landmine
milestone: M1
depends_on: [LW-M1-01]
owns:
- patches/webgl-permission.patch
- patches/webgl-permission-common.patch
- patches/webgl-permission-desktop.patch
- patches/android/webgl-prompt-default.patch
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths:
- dom/canvas/ClientWebGLContext.cpp
- dom/ipc/BrowserParent.cpp
- dom/ipc/BrowserParent.h
- dom/ipc/PBrowser.ipdl
- modules/libpref/init/StaticPrefList.yaml
- modules/libpref/moz.build
- browser/modules/WebGLPermissionPromptHelper.sys.mjs
- browser/modules/ObserverForwarder.sys.mjs
- browser/base/content/popup-notifications.inc.xhtml
- browser/themes/shared/jar.inc.mn
- browser/themes/shared/notification-icons.css
what: |
Landmine L1, read AGENTS.md before starting. Applying the common half to
Android compiles librewolf.webgl.prompt defaulting to true;
IsWebGLAllowed_impl then fails closed, RecvShowWebGLPermissionPrompt
early-returns because GeckoView has no XUL <browser>, and the only observers
that would answer live in the browser/ half. Result: ALL WebGL dies silently
and a smoke test of "installs and browses" still passes. The split must ship
`lockPref("librewolf.webgl.prompt", false)` for Android in the SAME commit,
or set the StaticPrefList default per-platform.
acceptance:
- "desktop tree byte-identical after the two patches"
- "on Android, a WebGL page renders — verified against a real canvas, not just absence of a console error"
- "librewolf.webgl.prompt is false on Android by default and the mechanism is in this commit, not a later one"
verify: "manual: load webglreport.com on the Android build and confirm a live context"
effort_h: "4-8"
skills: [c++, kotlin, js]
agent_safe: yes
isolation: worktree
risk: The failure mode is silent and passes the obvious gate — this is the single most likely way to ship a broken build.
- id: LW-M1-09
title: Split xmas and fold it into the target-aware patch lists
milestone: M1
depends_on: [LW-M1-01, LW-M1-06]
owns:
- patches/xmas.patch
- scripts/librewolf-patches.py
shared_edit: [assets/patches/common.txt, assets/patches/desktop.txt,
assets/patches/android.txt, assets/patches.txt]
tree_paths: [moz.build, lw/moz.build, browser/installer/package-manifest.in]
what: |
xmas.patch is applied separately at scripts/librewolf-patches.py:121 with the
comment that not all builders use this repo the same way. Its moz.build and
lw/moz.build hunks are common (they are what puts lw/ into the build); the
package-manifest.in hunk is desktop. Split it, move the common half into
common.txt so it is no longer special-cased, and keep the desktop half in
desktop.txt. Preserve the existing apply order relative to its neighbours.
acceptance:
- "no separate patch() call for xmas remains in librewolf-patches.py"
- "desktop tree byte-identical to before"
- "lw/ is present in the Android tree's build"
verify: "! grep -n \"patch('../patches/xmas.patch')\" scripts/librewolf-patches.py"
effort_h: "2-4"
skills: [python, shell]
agent_safe: yes
isolation: worktree
risk: lw/moz.build is how librewolf.cfg reaches the package — get this wrong and prefs vanish on both platforms.
- id: LW-M1-12
title: Reconcile the shim and delete the split monoliths
milestone: M1
depends_on: [LW-M1-02, LW-M1-03, LW-M1-04, LW-M1-05, LW-M1-06, LW-M1-07, LW-M1-08, LW-M1-09]
owns:
- assets/patches.txt
- assets/patches/common.txt
- assets/patches/desktop.txt
- assets/patches/android.txt
- docs/android/PATCH-SCOPE.md
- patches/android/README.md
- patches/disable-data-reporting-at-compile-time.patch
- patches/eme-permission.patch
- patches/moz-official.patch
- patches/msix.patch
- patches/remove-pingsender.patch
- patches/ui-patches/neterror.patch
- patches/webgl-permission.patch
what: |
CENTRAL CLEANUP, needed because all eight straddler splits hit the same
problem and none of them can fix it alone. Each split leaves its monolithic
patch file on disk, because check-patchfail.sh and fuzzfail.sh read only the
generated assets/patches.txt shim — deleting the monolith before the shim is
regenerated makes their own verify fail on a missing `-i` target. So after a
split lands, check-patchfail is still exercising the MONOLITH, not the two
halves, and librewolf-patches.py prints its out-of-sync warning.
Do it once, after all eight land: regenerate the shim with
`sed -e 's/#.*//' -e 's/[[:space:]]*$//' assets/patches/common.txt assets/patches/desktop.txt | grep . > assets/patches.txt`
(the exact command check_compat_shim() prints), delete the eight monoliths,
and reconcile PATCH-SCOPE.md's counts and its live-list table — every split
moves one entry from desktop into common and adds a file, so both the
`N common / N desktop-only / N straddlers = N` line and the per-list table
need recomputing. `board.py --check-scope` enforces that they agree.
While in PATCH-SCOPE.md, fix one justification LW-M1-06 disproved: the
LW-M1-06 row says pingsender/moz.build "still appends
pingsender_unix_common.cpp to UNIFIED_SOURCES outside that guard", implying
compiled code on Android. Textually true but nothing compiles — with
GeckoProgram skipped there is no linkable in that context and no
FINAL_LIBRARY, so emitter.py:1038-1042 returns before emitting any source.
The classification (common) is right; the mechanism is that the directory
stops being traversed. Fix the reason, keep the verdict.
Also correct the LW-M1-07 row, which LW-M1-07 disproved. It claims the
common half "yields a broken image reference" on Android. It does — but
upstream already does the same: unpatched net-error-illustrations.mjs points
at security-error.svg and no-connection.svg, packaged only by
desktop-jar.inc.mn:148,151. The patch swaps one desktop-only-packaged
illustration for another, so it is NOT a new Android regression and NOT
L1-shaped (L1 changes behaviour; this changes nothing reachable — the whole
component is gated on security.certerrors.felt-privacy-v1, set only in
browser/app/profile/firefox.js:2965, and Fenix answers every load error with
its own ErrorPages document anyway).
Record the trap-within-the-trap while you are there, because it will bite the
next person who adds a mobile jar entry: it must go in
toolkit/themes/mobile/global/jar.mn and NEVER in
shared/minimal-toolkit.jar.inc.mn — desktop-jar.inc.mn:10 includes the latter,
so an entry there lands in the desktop jar too and breaks byte-identity.
Finally, assets/patches/android.txt's header still says "DELIBERATELY EMPTY".
It is not — several Android patches now live there.
COUNT CHANGES you must fold in, beyond the per-split arithmetic:
LW-M1-05 resolved msix to a plain desktop-only patch, NOT a straddler, so the
straddler count drops by one independently of any split. The desktop.txt
header's "Seven entries edit both shared Gecko code and browser/" is wrong on
two counts now. And LW-M1-09 folded xmas into the lists, so only
pref-pane-small is still applied from its own call site — board.py now
derives that set from librewolf-patches.py rather than hardcoding it, but the
prose in PATCH-SCOPE.md still describes the old two-patch situation.
acceptance:
- "assets/patches.txt equals common+desktop and librewolf-patches.py prints no out-of-sync warning"
- "no monolithic straddler patch file remains on disk"
- "check-patchfail exercises the split halves, not the monoliths — verified by checking the applied list"
- "`python3 docs/android/board.py --check-scope` exits 0"
verify: "python3 docs/android/board.py --check-scope && ./scripts/check-patchfail.sh"
effort_h: "2-3"
skills: [shell, docs]
agent_safe: yes
isolation: none
risk: Until this lands, every green check-patchfail is testing the pre-split monoliths and proves nothing about the halves that actually ship.
- id: LW-M1-16
title: Restore make fixfuzz and stop mutating the user's global git config
milestone: M1
depends_on: [LW-M1-11, LW-M1-15]
owns: [scripts/fuzzfail.sh, scripts/git-patchtree.sh]