From 10b6b1515130684027be6d459d293f597360de47 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Thu, 16 Jul 2026 23:24:46 -0500 Subject: [PATCH 01/14] =?UTF-8?q?packaging:=20bump=20pins=20to=20MM=201.24?= =?UTF-8?q?.2=20/=20libmbim=201.34.0=20/=20libqmi=201.38.0=20/=20libqrtr-g?= =?UTF-8?q?lib=201.4.0;=20canonical=20.dsc=E2=86=94salsa=20tree=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packaging/README.md | 15 +- .../altered-salsa-tree/libqrtr-glib.dsc | 49 +++++ .../libqrtr-glib_1.4.0-1.debian.tar.xz | Bin 0 -> 5084 bytes .../libqrtr-glib_1.4.0.orig.tar.gz | Bin 0 -> 29521 bytes .../ci/fixtures/altered-salsa-tree/pins.yaml | 40 ++++ .../salsa-tree/debian/changelog | 92 +++++++++ .../salsa-tree/debian/control | 69 +++++++ .../salsa-tree/debian/copyright | 65 +++++++ .../salsa-tree/debian/gbp.conf | 11 ++ .../salsa-tree/debian/gir1.2-qrtr-1.0.install | 1 + .../debian/libqrtr-glib-dev.install | 4 + .../debian/libqrtr-glib-doc.doc-base | 9 + .../debian/libqrtr-glib-doc.install | 1 + .../salsa-tree/debian/libqrtr-glib-doc.links | 2 + .../salsa-tree/debian/libqrtr-glib0.docs | 3 + .../salsa-tree/debian/libqrtr-glib0.install | 1 + .../salsa-tree/debian/libqrtr-glib0.symbols | 35 ++++ .../salsa-tree/debian/rules | 11 ++ .../salsa-tree/debian/salsa-ci.yml | 4 + .../salsa-tree/debian/source/format | 1 + .../salsa-tree/debian/tests/build | 31 +++ .../salsa-tree/debian/tests/control | 3 + .../salsa-tree/debian/upstream/metadata | 5 + .../salsa-tree/debian/watch | 4 + packaging/ci/test-verify-upstream-pins.sh | 36 ++-- packaging/ci/verify-upstream-pins.sh | 113 ++++++++++- packaging/keys/README.md | 34 +++- packaging/upstream-pins.yaml | 181 ++++++++++-------- 28 files changed, 713 insertions(+), 107 deletions(-) create mode 100644 packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib.dsc create mode 100644 packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib_1.4.0-1.debian.tar.xz create mode 100644 packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib_1.4.0.orig.tar.gz create mode 100644 packaging/ci/fixtures/altered-salsa-tree/pins.yaml create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/changelog create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/control create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/copyright create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gbp.conf create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gir1.2-qrtr-1.0.install create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-dev.install create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.doc-base create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.install create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.links create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.docs create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.install create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.symbols create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/rules create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/salsa-ci.yml create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/source/format create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/build create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/control create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/upstream/metadata create mode 100644 packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/watch diff --git a/packaging/README.md b/packaging/README.md index 76c04cb..bf91726 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -46,7 +46,7 @@ QEMU (`--platform linux/arm64`) — never cross-built. The script injects ## Provenance pins [`upstream-pins.yaml`](upstream-pins.yaml) pins each source to an exact upstream release -tag and its matching Debian (salsa) packaging tag, and records a three-link chain: +tag and its matching Debian (salsa) packaging tag, and records a four-link chain: 1. **Lineage** — the upstream git tag object + peeled commit SHA (re-resolved by `git ls-remote`; the tag is never byte-compared to a git archive). @@ -56,11 +56,16 @@ tag and its matching Debian (salsa) packaging tag, and records a three-link chai checksums. 3. **Artifact** — the `.orig.tar`, whose sha256 must equal the pin, which equals the `.dsc`'s own `Checksums-Sha256` entry (copied verbatim into the manifest). +4. **Packaging** — the `.debian.tar.xz`, whose sha256 equals its `.dsc` `Checksums-Sha256` + entry, and whose extracted `debian/` tree is proven byte-identical to the pinned salsa + tag's `debian/` tree via a canonical metadata manifest (path, file type, executable bit, + symlink target, content sha256 per entry — stronger than `diff -r`, which misses exec-bit + and symlink drift). `ci/verify-upstream-pins.sh` re-checks all of it in an **isolated** `GNUPGHOME` (never the caller's `~/.gnupg`) and fails closed with a NAMED field on any drift. The current pins are -ModemManager 1.24.0, libmbim 1.32.0, libqmi 1.36.0, libqrtr-glib 1.2.2 (salsa -`debian/-1`). +ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, libqrtr-glib 1.4.0 (salsa +`debian/1.24.2-2`, `debian/1.34.0-1`, `debian/1.38.0-1`, `debian/1.4.0-1`). ## Versioning @@ -74,8 +79,8 @@ ModemManager 1.24.0, libmbim 1.32.0, libqmi 1.36.0, libqrtr-glib 1.2.2 (salsa | [`ci/tag-guard.sh`](ci/tag-guard.sh) | The release-tag contract: accepts only `vX.Y.Z`, fails closed on pre-release / build-metadata / missing-`v`. Sourced by `release.yml` (job 1) and by the version-injection + test scripts. | | [`ci/test-tag-guard.sh`](ci/test-tag-guard.sh) | Executable proof of the tag-guard negatives (`v1.0.0-rc.1`, `v1.0.0+build5`, `1.0.0`, …). Run in CI and locally. | | [`ci/inject-deb-version.sh`](ci/inject-deb-version.sh) | Writes `-~ceralive` (or `~ceralive0.0.0~dev` for non-tag builds) into each source's `debian/changelog` top entry via `dch --force-bad-version`. Reads upstream versions from each source's changelog — never hardcoded here. | -| [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins.sh) | Re-verifies every field of `upstream-pins.yaml` in an isolated `GNUPGHOME`: git-tag lineage (`git ls-remote`), `.dsc` GPG signature vs pinned signer, `.dsc` checksums vs manifest, and the downloaded `.orig.tar` sha256. Exit 0 on success; non-zero with a NAMED failing field on any drift. | -| [`ci/test-verify-upstream-pins.sh`](ci/test-verify-upstream-pins.sh) | Offline fail-closed proof: runs the three [`ci/fixtures/`](ci/fixtures) tampers (wrong-signer / altered-`.dsc` / altered-`.orig.tar`) and asserts each is rejected on the correct named field. Run standalone; the packaging-wave container lane can adopt it. | +| [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins.sh) | Re-verifies every field of `upstream-pins.yaml` in an isolated `GNUPGHOME`: git-tag lineage (`git ls-remote`), `.dsc` GPG signature vs pinned signer, `.dsc` checksums vs manifest, the downloaded `.orig.tar` sha256, and — the 4th link — the `.debian.tar.xz` sha256 plus a canonical `debian/`-tree manifest compared against the pinned salsa tag (exec-bit + symlink-target aware). Exit 0 on success; non-zero with a NAMED failing field on any drift. | +| [`ci/test-verify-upstream-pins.sh`](ci/test-verify-upstream-pins.sh) | Offline fail-closed proof: runs the four [`ci/fixtures/`](ci/fixtures) tampers (wrong-signer / altered-`.dsc` / altered-`.orig.tar` / altered-salsa-tree) and asserts each is rejected on the correct named field. Run standalone; the packaging-wave container lane can adopt it. | | [`ci/build-bookworm.sh`](ci/build-bookworm.sh) | Rebuilds all four sources in a `debian:bookworm` container in bootstrap order via a temporary local apt repo. `build-bookworm.sh ` — native amd64 or full-system-QEMU arm64, never cross-built. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → `~ceraliveX.Y.Z`; unset → `~ceralive0.0.0~dev`) into a **copy** of each changelog, runs real `dpkg-buildpackage`, and asserts the 9-package runtime closure from the `.changes` (drift ⇒ non-zero). Output to gitignored `build//`. | | [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (bookworm container) entry point. Lightweight, needs no built `.deb`: asserts the scaffold, the tag-guard contract, that `dch` version-injection runs on a **copy** (the committed changelogs stay pristine), and the real `dpkg --compare-versions` tilde ordering. The deb-consuming contract lives in the two scripts below. | | [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure; clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | diff --git a/packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib.dsc b/packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib.dsc new file mode 100644 index 0000000..31d9ae3 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib.dsc @@ -0,0 +1,49 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +Format: 3.0 (quilt) +Source: libqrtr-glib +Binary: libqrtr-glib0, libqrtr-glib-dev, libqrtr-glib-doc, gir1.2-qrtr-1.0 +Architecture: linux-any all +Version: 1.4.0-1 +Maintainer: DebianOnMobile Maintainers +Uploaders: Arnaud Ferraris , Guido Günther , Henry-Nicolas Tourneur , Martin +Homepage: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/ +Standards-Version: 4.7.2 +Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib/ +Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib.git +Testsuite: autopkgtest +Testsuite-Triggers: build-essential, pkg-config +Build-Depends: debhelper-compat (= 13), dh-sequence-gir, gir1.2-gio-2.0-dev, gir1.2-gobject-2.0-dev, gobject-introspection (>= 1.80), libglib2.0-dev (>= 2.56), meson, pkgconf, python3:any +Build-Depends-Indep: gi-docgen , libglib2.0-doc +Package-List: + gir1.2-qrtr-1.0 deb introspection optional arch=linux-any + libqrtr-glib-dev deb libdevel optional arch=linux-any + libqrtr-glib-doc deb doc optional arch=all profile=!nodoc profile:v1=!nodoc + libqrtr-glib0 deb libs optional arch=linux-any +Checksums-Sha1: + 30d3e079f8970acc398fce731e4879746fd3ff6d 29521 libqrtr-glib_1.4.0.orig.tar.gz + 83cfd9c32795869503c9a640e77724e6e76f511a 5084 libqrtr-glib_1.4.0-1.debian.tar.xz +Checksums-Sha256: + b57068934577b0070c2f180f3dfcd115ce19efec10aeaf877b8a99c9226aaa2c 29521 libqrtr-glib_1.4.0.orig.tar.gz + 3feec39c03c29824a6ce30b3beff6188501e771abca44eff64d9a267f0598241 5084 libqrtr-glib_1.4.0-1.debian.tar.xz +Files: + 08313a7983c619bdd8591d9efd32d415 29521 libqrtr-glib_1.4.0.orig.tar.gz + e2750926376de90a0e6c9c55d276af1d 5084 libqrtr-glib_1.4.0-1.debian.tar.xz + +-----BEGIN PGP SIGNATURE----- + +iQIzBAEBCgAdFiEEY/bM35YinQkoayrDJb+GUkr8weMFAmlhBx8ACgkQJb+GUkr8 +weMvjxAAnD5/YPOsbC+SQumamcV0ZV4DjHMH3vJoTU8JqTxvxd3FBsJwb2BoKOtK +NzjakK4yIgFi6WXmF7BdBVFkaYlV650FJFkoFmnJvXnVksw71g+RedhYVNSGEjMS +UZqMwZbQlf7sGDC2stIL4Jp5k/tAd3fs1G6cW6WPi+DeivDHlsxIN9XI5VEdR1js +FeFYVoI73dbQ/4Ms8teho6+3damnUtDy3JAHXb6DCY4vklI1kOlEWqVPp0FueEnq +WRqBBiEpV6u8PE3nK4zVCldKifJN1hJrujozkXUBGxB3N1Jj96jFI0mO+itg8Ar9 +gAhTWRpdDulWTVGRRltg8FPt7nr8tbogDyX2y5Ogug64iEB+apdmy2Ia+Xox5W44 +qp2K9cexKPdSn9ajbh2wpaqhqahXrHEgBh3JQ5NkB9qMqLZ/lJsVfFeo472PZFD4 +2U0yCPUgPD4q9t3i03e0DDJ61gnjavIRJ0uI82idUD5w+NR0X0hDCaiIuj5RmRKB +x2V3i4hoFlLX+dfRUGLqyx8e2a1H4tVhd9SZbNugmIPuTXNMb2DiHjE5oSoaQx+/ +ZaCh9UEqn6Dhlq6HO+ctnMK/+VlVKFcv3KguGEh0TBTDtVNmCizy7c54OlvB5wUD +tQZWGQHdB9kslTrh+17hF/yLG8I/hn2ZTCcppB/DDIbAZEER530= +=zmBE +-----END PGP SIGNATURE----- diff --git a/packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib_1.4.0-1.debian.tar.xz b/packaging/ci/fixtures/altered-salsa-tree/libqrtr-glib_1.4.0-1.debian.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..e8d3c82c881a598339ac3a8e04ef229c9e20cb4f GIT binary patch literal 5084 zcmV<26C>>XH+ooF000E$*0e?hz?>(5zycuw761SM0000zFM1{5pZ^n-T>vr}NNor1 z@tIyK_)om_N=i<6S-%T;fY@YJ$9cW8uWICeAla!;HO#xw*r(yE#{`)~G`Ui?LVXlA zRhbPYN9h4W2qYehn>Pje?UJK;buZ}fzaI7?3H)*7%^Fci*)!<>r;fz;GS?l5j*#*K ziqm>X;(W@Wv=e`JrGpAC`puKRFa5%o;5+??&d7Gr6Ho`^$sBlXRapL5eh;C#$vEFq zT(}Ob!V-GhkNnB1iiFFU;SVvV<4P4}o8BBHt4;NVf0Tfa&v3eAI|}fL`_iTdtg`)= z5wZq@vftbE50Rf)P@P+#5p|OCk}fGUN3)OXVF9sO~cP?>>*;If4 z)z7PU=(RPaTP4E+As!SBPq%7YlS1A-%df?~H!jKeI8~WJ1U&X&pLz5UiC@H4oc5BO zQ$z9G;=2P>`PuV;(t!LZ_yqiqe=*_*H4mYA=XPIhbcN{6%3E$npN;V9N=SOER{NVO ziYD65V^mqVF`4Ijh~xviOjk5HeBNp0_b$CumT-lYrEtcYCc;uIBL8!#fFd+7kk!-B zlEWcht&{J^g9a?S867b85LPQApz1i%JTyMDn%_8PR|(&pan*qFy-a7XW=U}}j@W0k zj7OYiccTWS>mD=>F3sX1|I;36l|ko!oFV0=2gGI*utwIQq|1_Jw) zszU2xx)7lQl~f|tx+FXE6fk*&9cQ@ zBblcv(G|=(dCkq|0ZI7pHp1TQM`6_{ z>N?sJmj)nRLa*d=f$ksU0jMpTnM*`2G0Erd)onJ{u3%%~>e;(mB3-TUC%P!mP2l*9 zW~cz4)3JX*s7Kj+aIJ*<)X77E(+@YOLc(nuO8A;OUxFj57w(Y#~6%;GG2kT-ynxP+mvC`vJx zE%u;a-6niCu#S_fk^FZP+V3%?LX*s~a0LWfW zD<4p4U~Xv+?61T6l@u~dSDglLAF>F*n8`tg_uUPXdPt&XZ4N(glzm4rb8Ft>?PpbR zQN?=x^Fc-c8n;cEUT&N5a<|amB#%QW*b3lsA-`6Yb5a(QT;#9J~xZ} zzX)4-BvUS!Cy2+QGrv-=pSS~BF9}QDALj0>UbL4W4z7t?Iy@Mq!R|9fye+FS*MA@Nlu8b$HEax(!ag` zdtiHD#THB1f>QJLI>}5~dR+`Vt3kS}rcPNZn(ywIRAYCE2mcf)3(-7wx%f4%ecB#d zQTl-s6PT8a&mKg-Z5WRekcQA0CuP((=xkSy_t>sI@Me|iN4}2DzzGy#?XuKX{E>CPZE{97qR{F1W;NGeO zKnL?i@oPXAZv=}$CABZTG)&cHEY?euoCpAfn*HL|@58l&#pliFvv9ZisIQNe}D+k+M-lxFoDACBWFJm78PE7<%s=XaT?T^1v_W29y` z4!XH{p&88n=c-*#XV_}`XVot!UV?4{ATZ&IMWMHFBw*z9%-PHO zq#xu6W2Re!&GPbfkDwi+x~4QQw)t{)^Q#Dp9)QS`&e~IH&YZOI!2}?m!Ajs(>4`Wb zWnY2{U`!NtiaQREaq%V*JTkj1(RsVqa9Gy)SO5TD2U6o3(_K-MAKBL}LsQ=2+h1Fw zi$FWgInSjK=~p)+Max#T9Amg$zH&?g4?efJAJI#a6XQpoF$dlMIBi@P8WG?L(B}Q5 zI@x-x0Gj>8J$^!IefdiONA-%38O(ATYG)k2PWe?7hsGsLKiZf1$Mp$|I_L$M;de3M z$h`oR!qNme-0nsK*in7yFUZ4ctuU2$O)sc|5^Yj8=p5U{ruv=sl%bun7I?q7V=1w% zI$eT`e?oeStB1q#gZ`uPnhpFg?fnWgx&D{`m|)gMXiV@`nDr0v=-i}9XY9|%>jBp+ zeM^$W_>SjkcGqH?h)6XYt9jv7#Zb7(2O%~>FSPAg3Oo%=u_n?xI0l+E)_V6mz}VE^ZJWPW$XE<6Kfs4JPqx3qE7MOsE|)n)vhB zBs*+JhWEpizgwHu#GMdpesv1uir zT<7Q3LXKF8U=hu@@tE*Ju0+4XloCFi^B29^jpf=;E<-FijKg0)jM+4bGefS%Lwh?X zf31jSfDem6)&AsQFed-kFxtL21kOAehch1`MwE(`_a|Btd^F(I{An2GuR&#C4DpQa zqTRNI^BxT~85$B}Hc0zgd{>PY65U zF;-;N%bh6RumJ}|@|@!?HtE3{F5^>T_#I7g!+6^NpBp=hV51FNrjiP--ZU3Tj5Nw` zSH{){F~-RZN6eh6%Xi!Z4g^5^G zC6N;Qr;h*qT1J7-5UM`3OdTi1_HH*;G4h6v)Jq@zEu1n^uevtb z+8*)3E7ohy>ml&D{OE40=T)5h> zA*)Hhv^ZW5p=>%zz}_^BOe zF3gZT-c~(Rw&#dX$P@PdHda;d7o|QRh(29t=?;%Vy4bG|qmJ+9=HJb+Lz2!LIFwaa z>o4lKF^X;G$-1i)-9F=iMz&a!LEZJ`dE1dLEvmz{jc(bU%{D%#5MOwt;%@xCH|=sg zQf_)4zW*?d9@N(CW3|Qq+`-3$E){=V*fK%7+mEiPZ&-9VtdnD(datnKqFTKy<~60d z@xxec5^`D9`Ir4(Uk8MSo)Rlct)<3`?E9h%LejJO63X3c=$_*hyFA*CqeXwl=#nBd zlgOfdjrH#Cg-m1sY-fHwmt!v{{h<0urFS;_$+yr157CV`g&_wVltUDoC!Xs>W)qA$ zu}u}tTsfGvT9h|~RXXxKpSEBge;T!sLUD57!{8>DM7} zI@le>L>GQ^>^*1{Y-3OW)v22?jY^AGoUMQW8Q);%|DT55+MzuV6j>Z8YX|g{--Jw6 z^LUCLhZzlM&P|IOZxngb0x5M_sXY*~d_tUjfrCBbhF{XR7csXOCnvAF%NrqVMb!mH-|k9IivmOQ@jkbOliYXq3$E0`2PbpZ+WE)@>*z*9L@(8Wc%P{3a=Z1y4gH1oD;{W-+VUWy%x zA0)i;9su@03lHBD$BB=f7NH~PY+!=c;oy4f4?@twpp|Ys)=}RRNvCAlA`m`pZ477H ztyl1^jL0<)OXQ#RwiX*-6f$X8@A&At(a_ptA1GL;-pD^g#&UgBC$CY%Oq_Cw3PLeq zV;jgzMT>%1jNqWwJ`PxfO?ct2Qz)Xhf!oHEpCOoB;pW@VMGm~#mLGZN?Z{z}SMC}! z_KG)Tz0=?w;T_5HxFfLk#e{4Ah499FDuC_+q?`MsPeSL!!ejyrV*d!S*;f$@?{(-qrj?Ui z#DrrsTfbxlP}kX7kqSwL*c;Fqjojt@51blI-#?Q{FA9y1*RB=GqvmJ@`3!=f3(5eS z3m`Tk#@xigtNt>~B5}E0s1U4q9VXdfw9>#D;X_z7|D!(*9GER`QLI)X#~mA%uBn*LO6G7yuTd@-#Z#>exF-LB?l z$VLq&mwaAQDRo`JF>N5E&f6Kd_z{3T3|;{WlF&?(8F z|C<`Up(S5+J(IfaW2X#>vA3FB+!RaXhc(d?#<>R4qX)=%;G$WWuJFfAA%G{$-&>Rz z)cT=jPlSn_L$6`N6XxkP5QmaQ2n3{;YnWAzJU7YNG5xhhX-3|8hX$W@hP{#|K={1A zd{`7#;P#YBzL)^WfHn2ZruBYIF~o4|#?^sk*=AGl(QQ?}s@~{6$lW<;S=xbQmIb3J8t|LY{9O>Y=sNCd{BJ*6VUe;kO!tIN1vm4^U<5{n@(C2zms zS~j_PeFp}5lPJCYTU}zKr9d)7&bARL88GiE1={jIJivQ#Q#SRo$1+W<_@aCj`{Uc) ze-K2Aek9As$@i7}jE3`ZJhIDHq~eY^(W;VPeEO-iCKXGnuIE2Ljxrv0jqX{mOkS6z zQ{{>;)Ld86tFMFgdb9uj%ZfE$cqIqaehs*bn*#exNGR1l9EqB_S4ZttP8>W1^)0)O zd|oNRAp(=}AopNV9wACY#6OX(VjH9)hBue?RF>{cP}{p`Z&})yM1iHXf)%LnltcHa zEC(#)>_Zy7Ivw&skq(klnr%4{KLODNDfgQRjxGLkH&n-8<5jTh5pgwz!%92UK^oec z2}1*3%+3E^R1y;Vp!g%tI|vBB0JfP`I%UgI2*tMa0S4P{R>(H;x1HUml|tnsVM6gj zB;qr&XSa88%cxtg{FP6s3QyF50S3^tv;8?-M5P19GC3YQxxErEj=v-t6wnaJHpmvS z6t1V@HXqw{{PimgeWmZe__gGJen&^J1iKpCKP3Z_I)|QQO|Grl0Zh&1Hsl@R7(a-v z`8@VkA(M zOHIaTjdm;h$9Ed$w>c?8_Q+sAX%m-y8cAI|c+iZL+)X09{>ul5c%&@+hYU{~%wx!drbn?|sn&`bjHcWT2!QSI^ zXY273@mur!`SZ(ta+7>;rR$C0m)@Yi+u!PatQ|c1*fr1}9zWi@-T&eDw*PMr_IG!` z*!wj%9zXx){{M6I-`ANbtxIh&GVIc@ds+XxI|IlUpF#lt=K9BN$!D^3 zl@{Xrhwp_hvP(O>%uS);c|H`SGn32GDFcs2m$R`cwV$d~$62QE+Lf0>Qvil2^eoH_ z6#w?HpLQSi$3MM?ff=NkOJB}&96&_shvu*NY*&;jxty7-9Md=P{X~0P^pncu+54rz z+v_R*))M{K`ur^aADPmO3hVUUYuxAmTRVHp{=c)ov;EosKf&kW|AEVmym-{V_NmRZ zc$C7=sqWjV{On4<|Ig3$|6C=#)b!_*{Ps2O@&Dbu-TgKBfAHD=Kf%WoX zQ)hqBX?Y-rPU}qjtI|#%rKVTv$uw7x_hFQjP7e>{_LHsT@e{qJAAhA)vj13pwexs? zf4Hlk>^~WRsqF0z(_J;pXvWawv;wYC(|S}TRZ&)GIzoUFM%ptMfG0}@Ai@DZdKwth z=~$;%^o78H5Phs$ZE<<6oKZ>6vs+)JJbC{5^4**Bi?_#zFE8I7zj=KiCpH0YdWo|t zOH`3PYI;CD>G>&4I{o(D#p&x;mw!Hfdk#Mj$RVoRi$-)#rs9sPG{a9!cwV`iH zz?MPB{b6SN^=qE>4I2c%Usf(WB^x-bj> z7L`>dS(I>?&H42!>PyGqtXi zN3UPLJiR!7_T>gzRc=SJ*DGv~7WVM_(7K5#pLPE8yN_3!$>_7ab z|M2V1ru@Snq)VT5zWh^1{`h13wO)ng^rcHdR$6W?i-N?Fj;-t*fjWR6T6zeDjLziq zqt~*vvLt19nsaY(j!WxDLlb_@Y@tJ73#N|3ZFx?^kk}=D1uObTY`wO%dM5g=*JU+j zMnPj>`9pK_>`~>N>^+}p=d62%^&ET~l~SwgETV+Y5^4~ACwJ%XH>)%wxlC+)9wetG+DDSYfe;SGZ z)+q1x&(FdChwm=Fd;RwO-Zk#4|F?Hn`hVMx2cPTzPw^2)m24LXlgcCI zDsN-@`=kCLr3c@PtR3YVz5Z($dRSym&*Yi%9&BCHP({!3@Mw8LuCF|d00{Tex{=B( zOsJoIv-q{I{n!7*zVmzd{2c#3J3Tsnb$)z)-$4-i!{2%Sv$M4~_Hv5s!4SIgL?^CNZY~E;o;(pk9@**KnbEkE8%LY?5XRsu zdE&H|=XO}ml+*IWRz;=|Wp?Fhk@kh$8^91%T;))0o|iDV1mb2`j^#;it?SBftuJx- z%R?!*wg-c)-e6~IAmzLBLm`i$w4Xz@CXJUNk-PlR!bP0z}gdh zX%Km6JbzcO9{!&&UDrn;`(Gr z3T2}6r3|1n!*xhdL|)4??LDMI@S(nxr%1#je}Jxm1;rG6m8LKZ%I+|Uv}6*8B7RdP zz#)7kF1t1T?XHAsI0dAOGvd>tnk29sXsQ2R+}YrHs*GbL>M+d1`hobn*txW5+~(1^;z z{=`{a9ti+gff9um8-bxI_^xR&dBPx2SX;vsjpxd$vJKaG)H4`tDsfj(#s0e zGsOb1*65$xgJuirE0&d61eyv#b}Ns`Lu`p}^pLYEl}Pld=sy zWD^j#c@FK@!U*8JD#zA&IU8G8C^eZz4qzWF^}pCkq^bxH;FV>|o}olWz(9!-4dx4; zP+UnsfO=RVLn^U2bgXeD(ee|J8Op@E%oJ##>!lLCBhlcLPJkPN6h`x!L)^$5WZ_Da zR=IMpHi|MKAA)V-TzxamRYBYKKnT2#^z%Pp_q2^!1 z0!*gFCQ*Sv0-&E5o;o{nYT}>L(3&2*DYpM~)?OvEU@zKhvZ+yUDT{PUotEsylq@`{ zs?ZI|I*oEmS>r?%R!B@HYJsA`qkwfm(MR%9Opi>{LR7utR~9wpG}R(BPVl%sMLfT} z9E2H<0EaS&c33arh$*}@iOEfAI69KngP_+cx}ALnk&f!#VmUO5_Ay=o>Ye z=3ucONvsI15&$@Dc5AVRONl&>RJtBPKcCOpXDC0~Oin%s^IeZKmcIOye4&7Z5;k!0!39 zf=-r5+wHw{VZjA!#0g|yTn@fZ#PUK?4Iu*It}qs`57boaF<{%U0x&=~VFX1`sC^eq z`mvqS0>R{f=EO56z{FL-Z%wpqj#PS`*|R8=EN8aoOFfknP^!%ioAqiI z#ztC$z!63x)uK*YNH9>MYbjC3)9V^f!gxrDD(pp$ZH}!U3c!${ZOGRKLM)*Wuo%V# zG`fZyv<8Tbqumn3j+R1hnqU|qYDl1t1&C^vK!7BeMs{V2mLrkjzeEU(Ky?8u_&6Q6 zidMJ)ZCx~_)B;8KYU4jT9t`W&i=haCoTdcKwHRdpf?s2>U(p;2V+vdJTKHlC?Nn`E zv}$n$y9yz5B@ue`LdMX*R^>5vE3OQ(#c13tn0jIuOh&_j08C3@lmr5VC)tpBFs^Ie z6$lap?98eG-h=2lgWn7!_Ax^-5sRNxl zqav;ZvknZjYZbAyd|Y&b9OUX|K?v1abJ0%QVmT>*S)>do8g_w$I=p)4wxS@>sp5J> zWEM;Thm*yG6cc031HVAikg(TUt5~ttSY8J_;ln=KxI5Dj-s}|P8LlocsDaI8*AQ8u zER*QQ6c=@gqFA-l2p%C5*0a1&E8_53juJKZ4-EzQ!bN{P(bKlk12o{6)K1s zvQT1f0(6}7b}2!$W5axrN)1M5kSt0I)X*EM6d^h=m?@X_32OkvFq+V-0fttDU&r{+ zi;3lA(?9`7uz3&ZAQA!|mX=(PQqn_;5mreuLjti1Li!Ij!KMuE4g9b|U{UkXRL?0V z48cWfjd7qxD08YGMks*<3wv-k4uWmC6N^$&8z#Cw39Df{cQzhKr@%?@R$v80)&_eO z0f$DFU8Aa^-k7L`s|z~cg&89R7(g_%lR&RiZKt_j(9E%Lm5I&f2s>IX)?c!;RhD9+ zJF2@}V7IHvj{Kl^uu9upkQoA^AYuZ2T9K^%9J2qUOPbUWrWD6aI>0U#EvQFlrl@XY zJx}mF66;|OS*se2@7DtAox)g%VT%+Y2sJ&3k*tdjK;{P1n$;CWJocTK_}E{(Xl6g+ zB#3JqI)^bxa%qmz087Ntz$OUERRts8NS6{MtWzWEG?0Lex{5qeYsw5K6&S@c&hj5i9a1H!WxON06DK(8|8j*nLU;=J;g@Orp zh#e()1nsi&!I$F@j@5#?6fWK^^PJcyqa&n@XsC%TFpBd-WYbaD5lPI&O^q}hs>VMc zc82u-Q1|`f`0dMcdHCwNJbL}=`6=EjJeMc0-@?~7e>r{iqAQzM4r$=a*UwK+PLB@p z0jh4R&&V>G=pp#C#@==lJEBw7!Kcx#pl}j{LMSO~(J3r-utPP?Ra&FnM#;(mALxi? zlhXWEXB38DNi`2`^^j;5r6^vi2)&wMaS&PvW8Ce58{AaK!{|(Q%Erz+n|*Pt2w%~6 z@>Z2)PE*kcO71|N++mlOo!Yb}TG3`xuv@FKtJz~TY8!T?BPO!uvw?uDgtw_slQU(FbhCr)Rs75hV){ff= zp4{njI0HdHFi;-@;a*V>%{W!-v+f0E)VW4J zK-3_s4B>e}LS*~SRWYgn6M+~uzJs^{P!HQp-I@fl0FgXg#b)JWZ=H}qcwN*cFFQ?s zI{i&lXy<&<@9l&dn6}TNEjdtQx0_4kNet&S2s@Uc`PJSl?1I`{Cq%Fge{~nG*=44c zL~J59-^4Q%pp7@X(6M8*fYhH?o|LS-w*V$lpm@0BhWIyTnI3}fLSd8ZIG$FZ+hLv| z7Nzb$LEBt$)KY>Tu?`$ceh%Dp7Q zZdK{={DtZ{`U@L&LqNC~!yqAWhh!-cH*Gd_qhbq1XWa1v%F~K#c0o?ts*WRPv23K$ z&=%Y_qJEJnsmCW54k|((jSFw!AKFbd{k2^bVF2?j{;)8|tLtP?Gsm#jl$(7j+oN#V zOYuS;6ZIGmV&knQhA@cqSHEc?pE{8@$CEw>eeP2ujJW@hcWDm~yjW}8%C}BeFj|E3 zsfo@^1;{OwT-oB(2ZcKYP;D!pGaC@lot-)~a(FVu6y?Xbd+M78Zbf5MBWs4F8g`TB z%1js%#+K<*d8PFfLl2l#?T0)q6gCtA7)&)%KOsW~SSs0S|xOx#pe-qlXp6vdu-7JXW+mXkLGx-F4@97P*I^ zoBAdkYEc*xur4F7?S-9$#x{}Ujk)#a5ff5K93mu+a$FJCoiM+vfb^`{p^sK68UiK1 zZO7h_Y1aTAc^-B*5Gn!pUNSyDd_%l5(VFfggk5;uz~%>h_75Ndqfm|ZIcZ=C4S_=E z!%*ih!GJzbPBs@uLfo~Xii6H%YKj<|K{KQl5D;sGIPD{GL7rAvk$ z!KN8T1wPWXd~n^@%yW#l0?^CQiVFUlgl9>tdW1y=mQJcCZ1}6S*sM! z0p~Ge*J6kk)BssEWMedfWC)|-5{TadzrG?wPRRxPP^+kL5fYgQN-prxZ6-D4iGUl? zEtYsLGQ*B)a}NIz3U-XH?G9+U-o52$muF69f8(RqIfu8aOHpWPSm|BSqJR_+b9g{dc zp+W@#3s~N;%7YF!T0Mz|dML3qx-ys|$ZJ1TbdA7TAP-oWJ*r+P<~r`u!UnO`&?87g zCko3sm8UNR)II|_uOK)xKihZ``Jn=$v13)tmnZjXvuRox+ZREm2vzFWRKb#4>kwE~ zeh|;v?$VnH4HFgzk)Z#rK#Ouv+B(r8guMu-?X;^2pGOn%keS*63Jsb4n@*F3K`Fv} z+Fg)R@Kik1qbZEkR*U)=n<>HINpTWev_OrnB_VEeOB%z$9*a4QNw@&I3wFUL5H+v0 z4^GpJ6l|wxi4;fxT3Ws|w%8}0g4FEKqSrO@1f4D`zqB-7pyInYG(of{aNn{f3$ue~ zL>Yz(-uSyfU^jGN8fZ9%R-zHwt}){+73YiNXB&?gsNKgcqEIJ=0^EncKMAUjha9v_ zHzb3KA}GNoe4yz}nYQKh2x~BGw;?zhTRB5GAm~!X1x)NV?&~3^bd#*C!+P{FbaMn* zjf*1$n>~TM`k)?$k;oA30-c4gCxj=SO3>0ccIm@XRHO>1$sKpj;+>0T6|+v*%@Zap zf4T8+9qn%&;($G$;?V^s{Vx9b4fyhCgi?xRW1UL-pkwMagv3gNaU+(;cL1JE{I2A+ z&8rC?K0_F=bU=VDk>>swM=xSa;$r#J;EoG_uKpnWug%3f3tR_Lj1nz7SUZC^PhD=k zYe>bTR@n;DrSy%rD2fIWUPzq|D(IFIo>A19Q{~tx{CH@Ny?(K{#?lybC->`af{Nx& z+N)q+g0M@WXWahaMm!>hnm|lZAF(WTg+S&SJ6#R%fU%Vi)aSgZNIbIExYE|&l=3-; zM}`~4JRV|%Tcbs|LKdXRnEo{uFSNHf437pfzD(HASBwG1IZa$(-oT=j_~H@%qvhOc zndUhZ;gG4rxe`|qR6ezvSb?#ShQg|g%cY^RG1T7xh_c8A@umjZU5w=1Va+s0+-6Bm zmb!Fkz-gxi=6CO|nwK5_|J!@h^~Q~BT{ypa6=ij7kqSvkJvouwy_+qu+uTu0iUtpl zcOM>#ge15*ChFYQP>pV0ppn`4S&h(VJPZ;W@8?$nz?~&-zf)WYn9++F6MEosBX^LzknFD0IME(thL>e%-aG5p*yF8iv{p>g7E?y#=12cY;C&$>xaL-_&!+k)0^feuuCpN?XB!4Ih&A zuEG%|F|Pi}P?90cqD|Yv{hfy+dS)hq61&CbWCS; zYn~H|kTER9iTW^eCexF)!PM#^Q;AL+YNYnvi+j&)4hNmf@Z^|nn-$HvUXpzx5|N!2 zZ+d}})|z&(NR(3H_|ctFk78?vItY$ISjq5F(Ie+Fc~2U5d4sm4u>nT{G%Cf^l3I_x z$&)FrS+SN|NoYaKEo!wkhXL0KS+2%2tuM0gg|sPmMW9>5>jnoun>XS_+nsuT)Kmo{ z8WVIKDiaFN<`LZS&n^Q#86)q>X=SotJm>`l7c{zIh=_GM(#AAfSt62YB6%_@%M_L7`Di{nC zjn8K1HKRCsCU$Ocg&K@9!Ps>Iq*WfCFP3n0?l-FfrCqTn# zs3Y}?9$EOJD39#g5&9m$CfG+GZf2igl4cS0C2Uf{BKy&W8{J@}2{Gy#V>jo6QCNZA zV6MxXC4vs4ie-awd!NH;>scvKMoCL+dXU+2em;r&8Dq4a3=S;xqdrnzNS513WTFVa z$t5QzaqK-x>Szu)gMnZ%hWIgJYfv6u)`)Ih4<_j$ZH4BcvJM!}UMw9jv>uDQ5%=bq z&317ITHAQ2+1?gYNhw{XU1CNHJ5@McLN}>0n6vywWK71mI02#TD#^HtT*Xsd%hZ>Q zOG%Hr>A`5inztVx)nomW5(=5rwSrE;NkYUqUEsKV)1JXZA%u=`OK={LbxE|eKyT+_ zfmly(7K!$mZrV_7Z86ra0LuQjL`#XDY|{#fzR1-NsEV*!P$Mr|mj8Le60;JjCB1~} zfg#9r*fIsGBf}VIMqTzP(F~rH?j$zoS~#1>NdR%loi;3;U*qr@W3;7OoL;74GSWU0 zGDysLpMx<4ZZH{$W5W%HL))Io)j5x>SDQ>_I&L5lClb#hRpaek$jQf;*p{%8hGNqR z2N3FPAKr8v3ph8%Mi`n7IG^Qxk9=gu6b_lpWA42ldagzhb|OwjUVOE2qB;$xI2u!p zeGhl*v2bTh5TaQ}o6;Qm9FHRLExGK?BsC3@bkH{k%Mh)gM)E-RE5`b%4wq!pz9W1~ zl!U3y$~a_rn0cbN$xjmUwk{kb8TdbZ#JpINRCtMEPB`c5MCvN) zp)85P!8$*Wv~Fgw*b~tf#C7Usr#SARm%X}{Avh99+?${TV~DI?N@H0y$&90Sr`m%} zyxUcCJRfJ!1t839k#z!V$+b|h7r)Rl!QD!A-V92}Rv}G~Y6wrj%**8=WU7=kAi*62 zZzGNb&&+;gGPQ;$N)prW(*mNgqpEw>qDSFVV{a)qh7&&^E2PcZKsk3ar^q;T6H|ij zj9sH9G8#kjx$4R^VwSA=t!sM25u*?x50NI4(82B1gTS$?q|OJ)anTRroWs`cpI=M?=xW zP`aoY)hQM94bLQcF*O#zE)z6ek|HJMBl(n3N@bZNI)wi=YnU9ddXC0#!#Lu^vyd^& z!2nb@9f(XaQpHO&M!O#k2}c4&GaL<@9z`{a_fQXNrRTArbgzwd$!P$)T`)dS%=2h~ z!#(X}!Xs{@#;qE0CfemVWajamkF4-~Z7s`eJv5sIyYo$lDAzpeOESVYny?xtR}BuM zL-Pto$PL|m327WrjCLxrZOZu=j#`f4C!_HsddxVe+osxPl^o-e&1I0fSDuBQBKU+- z-4?sU6H90!D$o@JV?;uq@7*MLB57;gOJ|mD|W$NO3yI6$jX)4$`;Skp73B47a>oO49nsS zCHP>xbu3H9tVdjg&H%a0-i$5v<pj>&F;-Ew>lypXbcJ3<#1_a>$FOM2I+`RBm(ew-7?x#v`k4% zK{tS-iI5*r0x_v(F(%Z}l>UXMR0p4!9Ur}NeVU(?zc)q?rV%ZWA&V2j-=xuHPj&#z zaX-VbM`q%FB9-WC`7?vo*IANrO_~J8o?dTL$^ko%PvV&|sv1lR31i9j#}8mSxnkQ7 zEeOw$o{Dp>N|a^pR#0P80<1KG&Svuyf4?W;t7&ij0D*VdJEGxyu9LCEEjG=sqy{k+ zfFS@L>uXI#sU@U@ar|(RPjx|;S!5LKg47Zc>QBgQ?qt=^$YpQRknss4prkP#oTY4g z>NXL*+GIAEne8Poeof`Euz1A41PD1WpCU_1$vI=cSIM$6VKKrYg&Po!!>F870!6dp zuPQ-cVPCR}5Ok)m{c^k-Wv6#Mj*_QRtcuh;RTKLl@6a9(Qnjvz#lC@ody#(b#0)x^zR{pyd z;YDOhSD?~~PwYcRsctkM!m^DxOrFEKVS_o7FlfuNO;8TGUd(7-Acj8e`_60PMafJe z3aFYmbCzO{LZ;Ihs`05osHo@_c1hqNZ*0}>zDLE-e>QKU z-bH|;2rrJ{bW7PV(clXdl|nR>PK57x5rR6NHxMESQ!|$hn||@Kmu`tQa_Z=E5#~3t z^_`*%H6WFQ7P5H=^?1p^AW?%$Zu%eB`MnAD&S{}ee&9WoQmM|SQX>hyKGcCb$D$wW zXwXWGq>r3L;A%MN$zlwkbkHYZ{sWN$XI_)wf`*{V8AeONvSzL$Uop5!m_e_m6s6@R zPCBYUMN>PmE@@rNre-FJv2u`H+z@36{gr&}O%dwn7bek#a!ywVlQECKfaAd`KPLl3 za!w?J!Xa(<9$uSit8SlVM$$;mJ2n}m!UknGWJXNRG73rcSB*=h6#9k@#jgxCXws#G z1)RVzM87cb!bz0ft$1fmW*PQOvdPn?_A8|`+*S!DC7a!qIGJY{f2UC#2&$6o5@N?W zxl#`CpIlkj6ul@$SLV@HI>{smBUdDgfuHCVkm1QoIsnOTOln!&$CFedG=yE8lRw|< zqLWq$`nfwH`GT7&V8Om#q;_bENi z+V`AK6q&v|86F(3t9nN0@rS}jDF8#$qP{M|SvaLz!Q%JxrWPJ`psTYwNO;dA<&m$+ z0{K|963#a8R&ZLV9tT`Mi2jKSF7kH^4IGKWyw$TsP&9jj**#sRCm0C%SjXPyuvjHf zY?B~OC!+|LTEe@7G0-O_E{gF=E4Zc6H0T8gls~X-LF3@rgd7^DyacDecqlX&_%>(o zBY5AM2-rG3PlG0j9TTl^6Qj|itCR4yD1JjW74ym&bCsb^C(OHc_tf{TgM-%Y(T~LA zUNqI)_Eziou&s{1ZL6Km+k@7@kE(O1F*3H*yMuOH?Y&c5-?k3kx9eE$pp7MV_bR$B zLd=PxT=x$3f@)w@2#Ry`Am$0YP`H?j2O8zkhf67?%om94jqGHM|(6f6)CH0$HqYYAKC{ne7i@jx1F61d=gi;t;U&=44q3Qo~>>jR!O=MVvXT99Sx>Ttik)xkgDIglRA^$s+zvb6_` z=x@h3i*Q3KQ7xEGtO4ner%d4zL|WU`Gy>n2Wof3fYQLSOKee-WNW`|?I%+9uG5q_s z4W%mw?OhlzT28I4t>Xh&T37<#fYuxy!(!{~@}wXE&|>Z!Y@79j<6L>y>g*gJXc{vk z2w&cVA;c<)*6f5F?!7yLbsy&Utkj*jZ(%ClwxL*S`@0UUJpK-> zg~N`F3EaXODl(3F%no`>ZwIh_e59an5l42STBO3Vo%V>t1-SVmGUi=8jYT^PCGwRF zwTx@WnLaj_LMPS>BDA=6WshZ;uVhy7P~5cSM&cmBb5g9#2($JCNF<)6HVD$kB8|t- zki0&|tWN4?8zKWmqrnLE36{EXFG<-|GH5esORaON)wstyla>_ zjFO3v^EWuD^u8OtGSsLVYK)#PuKEPX@>GE=(&KPt#{DDwj@ML|e^$gd7!~sxzCcx= zI6~6cYyPglqZ$>}qq$9!cjl2AOXsVSU~WLSbBM&jTTN#Pa%`3uB@<87EtK~*MUSJBhvVwjyJQA)?Rn*3FO#RS6i&`w?{4$R1Ld{Ey8psm0xUp%1 z_a$S1%+ZuxZcsNQ(>^UVKo|0a24#hY!^lAX@u(K-o#de)q)yZe6Bz;I8ZMoO1A5BN zr<-eJM|Kl!M6d(`BD7PVi2)E6_?Se^53Kp~Y3B97D-VQTPn zGKN881ndjKH;)nOz!@eAEk}%YqA)y}cX9;T z{C7$O!O(PT%duEBOC|!>jpEaxm&hc)6^zUt`o%AXuzWdT7T>|e&tVKdPOc_{D-84F zVMXqAa%DcmIQF&y#Ks~Do02#Jy+;1;yXoX)85WogxxfZq3dHZH@#G|_nfN{U#D8NO z)Bz0WWTd|B^*=>ZqP%Z7k`1#%fW(fjfPqcMuj^_Z_ULIm#B*-ea3A3Zbv#!ki8b!S zcR+7w1?nxT1-F4TJSX{Ax}g0--s-qO*Rd_+tF>=V!PBnbwO?`$G#Tv_3$ z0P){aScQ%LgV{KtNM3zhQk_rJG+_N0E~L-jRa*G@#dxjo;9+|zHlON?30rj)HP4#X zx2}%}#j{79X3_3|zIDVg(=L5E1vGJ6JHJ5J7oPRR@8S-2KAT-68>_1zQbeOsZ`^~D z&B^p^buj5C-a+H5R3)$(^GAaQXw?}*JTF4Syllu<=xA^C`V~kEFJ3mOuv@Jz)*1{b zBJ)zwBRmr|X`bukLYw(O{01Lt3Bf@)#XM*p;~qxuAfPy?05%CEF&M1uM9|proLzu> z@?zRry9RA_Ft8r>YMHp^orKA!*j5Z5?z2p*F4X)(E!!$iO04o755j5fJBhYejVg3 zoeap#l{nAe4vr2~^>6du5G@du&i6K6a)j_9TV<1g-*HOkVxAt?HUKsGgVu&@>U;z7GH2I zw?2hLS4iL&81F+ig_D@GFC2&|_C|;nT%p)h;q=yMfq`Ee6}@*|US2i{GdxN@%_bML zeO<<%;#F+j-l^&7)?`aUm2|fGv=<8$D|_|0iO#vgN+PB0r^TDMLGwR~8)TtsOfI4H z8&IWT7fChck?G&@bvypQ({7Ro@c-v8p9lE=^Z$Orz%_zM~h#QlzceYHAIrrv!jiNgOYtqM6$ zx4rMaB8(09F*LCYO{53Xig7&txu+Ui+=NQyc}*S6H5edexyNGWmt;*)r6_oLBUk~w zBu|n##-aL;rIJeJMNRF`lXLgjzMj$UJ{+K~2Rh-fcOtWTEGMOnFk*8S&nSWDQ>2#U zoelWF=)$IdP86}lC3&47#9in;SN&r!p=QKg_mkhE%XVjitDL-4dh1t8VMhrOyxXEcI60J z=~IjivV3ZfWseY=2Jut#3xeIn3YLfD3R)(KA<^AQpbe*kCIjjIgnxa6yZwRs<3D*< z|LkkNe!1yLajpLC(I@DEYFt1i-?a}8JA1pI{Q|==j-P$1YpMLXM*f3TFUd53OJJ3< zuUFkA2CD;OxzKmvD7PSC&MB`>))+PDd^Q@&jsAsdOaoP}Vi~{3EA-x~ym(3$@trn~ z{2!~sgdeUn@t@izy1eW{LwTwZtL5Zh-}HX^Kd(PRP0++wOaIgSpXMsn{nP(h`nUp5 z<$;#s(BRO77LoWy1^;!ntp14K-7H-{Yx3njA!Tpym3UI_3s5dlJDGwif2pbCi{Yfl ztkvXue|z*{#~u-yB6CB(Fq1q5M8+FAWNS%n752cj{7n#e#5+RLj4o6AOO-58ROKK4 z`JwgSdk3HKkIpXqQPc14Pm)0W|*2-oRug|yAlw%awHp|X{F4GmNZ z+n=qpvcA4}`z-qE!Vi?S3Y9h|j0}sv;dq`y@40t*-t0Y^$~X;@Pvm{P{0mKYRD?U8~tk`s!cm>;m?N1_ldH zXZNn}+(!NUQxPaoI+LtO5pHD{C2uo;i<`Wp-EKTn@Nf0~;AXHUO= zzW$$W|GB4yb+?`W!ykSU`}aqk`k`K_r8gq#zFDfc>L@&Eh3xX2Gd&v3$CQ4h!)7R= z991cUI&gxC%A*MbJ_&TDSeLZ@emPOrf=|h8lm2ALM*by}ga9qcSmu}H?DYhnx>hZ@ zy;M1wPA-$Ei;@}?(ZU)V2Qu>7XmSFILj&IF4NfpzOsZ=_`K5~WFyFGe)uqa1bOKe~ zwq2_@SA04}AGQ8h>L<1Ievj0$1{x0P@H3v!pC6&?SI|O|B9|k|;6yL`)kUO}<1{ue76(F4_dBRpE`^$7dgC-D zo&=g94OAVIdq#>s-%aXjH=1Fn6XpB^^|1k@v$|KViJ+K5^M1lu&gf%Bw*+f3c>lIE)>u6mPEJO+rU82+KYiO2AJBtPGrQPe4H?XN49 zq~9BNd($)8ot7JoX+#9V@kz@cD-{$#yM$@?3L{#{aYD?EZdPlyKr@ME)n$=Wmh0l3 z5_gKWBvos*N`(we_?}Z|L>#qRK@7-?`D{ZihveX8H1qK3nxPW(*lXp6GVQBZ;L~Qm zy24K_pT%e(K8v_wX(QB(;ao71&2_rZNedga5#Vo)8MJ|cx?PR}uP%!NX1PWmI*Ykt z=py>~yArNjbv}vB!L1C<*ss{?hV8_kt7;jms#F+xxjRF&$Qd=+?KHJc#_K0Ds&y@A zv4`y}p8mA<@ni1DHQubfS#C1NukvfLu~*i3L@{&@y89EE)^yi6Kz&(zC70!QqXDB6 z&FoK}kXLUyyZgsS@GbLzo6~>}zk0KGd<0LOhvP|uB8)E(4HSxa^cL+>b!|++gnhes}Lf z+ev1q=odxEN26`*+ zcaz!R)!BId=fCJ%vfFou7|QtSsomshwRAnHZ>{;_8MRQ0sn!OUBhP9pT^Ff)~NI8%H*TikKlW{N*`igS4mM1q2@ z9LqO#gSABJmyx@=kl;&a%h?CIC=doh6laUt2g|y7B)&=&-DG~kFZuI;o0slBmB09O z<~1kXz*Uu3x5PkWnu#7ceWHmMpImLo(*`E~ss6@vqgR7>8yd6404ni;337ZkpJJvH zH6t4dgScV(h;2k?!aW&gz?{@*J@ZrgihFX8dBlM;|A?j&HElvM6>1KC>1>KexKsrJ zoFUzu2W~g^e@pspTR`^ zgD>ldLRhdB!c1a%U$0;YI^MK%Y*)iFC=1Ou>+s)Gt2F4E8VX*m^Z)4@4IRer8U!Y` z2AbK~ctU1HjtT(14u1pb0{uC6;TNkvNYtW|&`Hdijzj7SYTXw|oLG^Ns1rWM?4;7~ zn@VsUY2fuLFx?uGvc92ChrKfw(0h1^hto*JqMQI2+w?2W?SksN{37V+efok)u}l0x ztL|fd{2)#^(2@^=5x6&fh8dlo;+%pun@~OU=KEH1HSQnaxd2=6@TAxOlvqIOY1A9) zQ&{M0&f%;grJB}abq(D%vXn@B>8vr#ojr@JpM=jT;{e_Z%upTZQ6MN(glp@aU55N1Zfds4!CP)L>62ei?d=hvcL*0eY!|d`oezf%Jc?Z`4)%$lRWy1q zTIcrmG#a4Ziq;$GM&dqT`f~4JJM8+Ro5?wdqnFNaCdyFTlKI(LlsH52ipRz&LCLTU z5j8XSqw#!XZ(;`0sr!>y7Ss!qKWqE~{Y1uO3lJCqEgEfTg2fz-2k~j8(i@OLkOUaJ z*z}sLt08QHbKoKyikW$OBg0hFWw||w&}Zn_HJ3?oe2P-6Z_!*CV-T65F zHB?EAY&rgwDq_Q_R`m5}Sxom|q0*TL6XT`ChaX|@GJDHU#Pqh`?{waF#dh5N(An*3 zfTvg9lXMrFd>mDOf`Nop`f#SQ)(_p*&d%QVx2S8obBH)j-R<`NL3^u(2teKMTL-(H z-S>yf)FC}42@NAI(5UO%nq5!n2L4LKw2GTFncz2+CIHkvFqj=A2w=`2Pjxv@7^UR# z{MS|TdY{8ss%3J+3+rak_g_G}<8xIxkd)71kxb(R=TfT$D49Te|A{@tdb$(=s|RR# zLuFN#x-E-AvYE23zT%eMI@rsWb(9q8Wz1?rbvc>#JyR==vHlo41yYSOt^&7Nd=HwYt1U3*x@{mA zc9kYki+nJGvZ==dq2xYM7Cq2J26wkzgC0Ee+d@TTaQyLiilVLib6w-*!Z=Z?Jc+w* z(?qM3spVg(7Y{iY0rZ4(B-7v84)a@Gx3>Rjn@Mx_Yo2{EXk9}1e8L&@k?(w*Dg>um}f&wa$)`q%Q(&R^6NhB4Ujv}x0{-_n{||88>HK>k zGUw(S3-!7rpCtW=|+(67JxzO$z?_%C#l zh>iyzL=;~RHDDzf(PT}vr*U6x^``S=qP~fK?$cjy`sa93;CzHm{MR{eUe7CuJ*hm2 zPtj<8|-Xo$~*jfRF|J|JlnIf&8zpALG9s;(Ad1 z4`@WvBbKdk$$)yL5hQj_BNC-s#*ButevMuuG29 z&;gjz4*b6SnF-I#2@{}dv{h6f8H+-bsSVm}e4dF*e)G0<*xqe@Xuk;sB)itvYc-Hv z#Qr#&G3{r^w4WZ+er5#wJc1c>ytngg?S0tq?2y%aSzE=sJA3cD+j}2co!wVUe}f8_ z^doqq{X=`JyWcwc_SMpxwKq$CaIwUkNEn8V`e4U^OL)*6ku$0kFhrE<#S>+`{P-V+&C#4?(F#Xo1Fks;AD(>dKM}W=$EL6 z4d2N|ZiEiI+23`3_|V=!4?CXpXQ18V@p+HYsL*N82u#LVPDc+6y>YUE{1{#4dd)nC z75)^*7SWCN^ubHZNV-DMum1Q?{^PSSrIfz`I${H5mnBh7dkayz_I~d<-$&0O=Gu+% z{bgKh%N3q0FUB8T3>bo9CKPX0slN1F2G@wgG7aHD0cSbP|DDQzc7O1`Q7r$xSYLnkJT3pd{QAYC z{r@4Z->?3^{+9;Hr#ChNG;R=|ES3P#w!gu(+XJEPwR>+m9Z&pQuAkk#&+#0odsoS7 zGUQJ!e3KghrRw^LRwcmAi+ufrd_uE}Mac#T3t*W@=yqza?X7MNZ*LyCr_x8WrTSs-Z z#+)*Az3<{lX6+B{-J@=2_o#jFuC>*MmT(2F!yk9I;C|-_!z=3fZLg6>eLkXC1%=0r z?RVv?^!Lu*-d~URyGNZ5?Y-k8=fVEof%|i(b$C?yyy;bT*m=L(+Uf4@Z9{{$q09Bm z?SuA*z3);FOjR=Y@Aol!@$e$*s}(s{7=(XK)3kVj;E=$wjS@{?2PM-0hO zYl`c};6c@uIoPKY{~~EPEbU_?^C3ZKaUckhA(A`|{-ONj+nW{r+D@FzIQEgw4{|W} z^zn)&hy=g93-p%&>uE+!4j0DXN*}8ND;42^jT{CPf|%SJonw0?OtXb!+qP}n6Wg|J zOq@*YOl;dWC-#nQ+t}xM|HApyKcMRB>Z)GrE``<_XNMQ|#(@=N>J+6%cytc(`b?eu zaRY$`RE{o%x<{CV%&WD27-W&~Qg6{7HNM_X> zs@6p}?Bd=4)oPX%XKg}e%m2!lgnFx%THL@j`W|9Fcz$UOpvY;-qfDt*j5ntlc9Qex z`-kX0=4Qe073YVHMX_?g0p&p;x*Xj;g=zH2t_)HF1yM=-Dyr;rT}^TXA)k>U)Lm6u zM95Hk3_aFuGO+;`UL^sw9-Bg7Wanng+tqV4U)#QazJoTjOXTnGdj=J>#;OBt4nrdl zbeow_6&6aUQOUsd2f!Hj-2iGMvyFuz>OrxJ^lqC`Jv6=K^C+s0yOK8{wTL+8=} zUnak5jcwuc9etpuv`RMT!N#hCq!EiuIAXf2O%WEOa?J(b@^6*M$gaoFkW%%*p-3*>@Jfm~p?{xFNUk>e91Hv>g2S zAO~0bHOac(quY>(Bo<)ptC@vF>EG*#rrtTef1v@%WdnbXY_TW}0uT<%()^PWys>TT zB6MlN#Q-c81r_ka zl_)+)Nk^eLuI0b5&ahRT_qq|IW>`-Za*?f?+faLyMIYUOS(8n(;x#@Tv@?0erGLuU z@SVD@@fSFMIjF-OY4)_yIl34u@ZH^=ui7I|-Z5{L=JGsu39kaqZd!V|I6jMm+1sAR zu$#~u1Z+|jpI5Z#KDTU(!h16lgyUG3G(W_j*A#2NKaT6=2;lUJ^hB0&QZojYXztx6=Ob9PxvI1@@OQC;(Y8K}li^@d9xGxqZT^;40Z>gMXM zobI-?(l8oS&dYPuRr~I62e+rJH>wuq_v27oKs?cJO2Z5USu*L*aq{OKCzp9z^XKIT z1N(N^i0$@H_p{DalYl3;2{T8KlLY-0-VV&xz^0mPW-6x%ADgNpReD)?g1NkaCu98* zPeAyom4*VgEvc~_&}JjaWVvrOPtk*s1#^{%Kz>_^@Y1BMHEZ*cI3J%JR_4ymhr^al5pF>0pvDzKVrsz5gpn} zqQ6^FN|?s~W4fZp%h()q%wx^0zV*Am+77EgQ!CZQJ5Hg9s4hm!1I%s zG(~xl?}!?sc{a#@Pc5dr1tHu7)$YaD)eJ!)vhlV^&*Exd=AHs3>)zfn0BVr#DSJR`~Y0_53SP#E^j`Hz+O>g zBEG@3k?3FB%e(q2IO3|PqEkP|md@eZA|eSkpTxg*ko|j(D1|E{p}Fk&L*oqu4t2}s zAvh+ke`62m0{+;Aw5J-S|B&qI7|d3@HZ9}chYL@+fb~x8VEz$_WeSB`ta+Wd=TFRc z&B__a7C@)}5Gllc&h{vSyc%Ul&RS=zEFC% z=X=#9Yp=_AUnU`Pjt7oPE|bHm6V%ByIerTQ>|utCwp=iLeRl=7_=VOMr3PYGwuli8 z6J_Wu5Kbpefba&CW5XA!Moa-VizUaGiDl%YB$8*Mq_S>%9M6q1aUY zQHpDb1b{d-uzQ{aDv5M*l2g4Tra!L0MJlOx~*tO2>L{ z^l_EC4fSSQP0euXdjI`94GD@S%`Fs}>xW2W_OAa})ExY{+YuA}ogFS^?dtEV z0nbEG$I;>abQn|*KL)B4&LS{sMrEx!GRy`Ig`}N7x{4ni;3Avu<~tl01zP7GH6QFh z@CJj6lbcqpgQtYft|`Gl@d9_~fy<~wPC14X@J8GwxhhK?YHH$m`cDGcCVdu-NO?q8 zW~do0++^V{eL?mq(La&Kz3sdK%c}n{s!PiDMe;bg?YAyW16&txuw_jB>vrvbJ@O_1 z>yOtyVy9+X)$**D3^m47OIC^qi+5Zh?KbE{1(8){5Vc{kC#zmm4l(r~)rw3#nf1z) zRYV|JIG2)gExn;=(Z28Q*^H&2z$&};rw(?)FIGKDSjf|V2pp5$RRC2}gJetsYqc4s zHUHkzyq3Eb{d|c-(20DsffMbX+}`Hchoa}iR5&zuffuX3O%=F8Xt+Oa8>5RIV`Uo> z`Rm%4m_nFSAU}e!V+Qp&TQuGQ8>?{8D~Daz&e2M03+!JqFjg|u9M)QxZEBn45Y4c1 zY7zQrc9Bs^9;gOEZ%zgQkgdF*VCqROE2M%^zeRvY(_zJ&VemN~hfyx;3Q`7eCS`o% zDFT)aPe`76Cjc6l=#<1+aJs|GsgKJJFb7}hy6s2qrlgXYK{_T#mExLGu;ducm;oK)0A-9N&L1# zAwr5P!x4^e$3QI0#jdN$+A}P}?z2}lX|9<=oOYzDclFMxB%R`uNV>g>XeSBGp88## z`0k5eFl_5!F1vpTpXs-dhih%S&d6?TIV5gIWi}x|v-R7x4IhD1vs~AdypM^X+!j~V zfbhoQyS4lKr{K9zu?rk)Owt(-CiNc!*qnfy+@fWd%i*0|l6!iezUjz!4qj+C&O@@h z{XgvB+L?2=K%QS@{>=oDtJTzOgID{I#;F`OV!=m)y6D`WV~p^14&yO?w~8~dQ^gND zCS(d@k|t-nmW{@?L2VSDv^>em%I0`?Gym*mB81&yK1IzbD!6Vyq!A#_TZ6fM&ZS z5+#TFgCk-o5HJl=&gv!sUbb-OtNRe;pETiV0&^s&gwP1_aeZ%i*sa4tI@*Syr_^cN z#MIezyA4m689t^S_C<$ZdEtn-D!3d*7a3FYJeN}d*7BlWL#F}X% zPV6sNVmFg^I6{!6jJ>%D5@#20HmsNY`$O;d;ZTYG7T?BL)S<;%?gH!@gSCT_s3y9( zEE2MAgSK0o79_Ze?j$lBv{IeYZBBB8x-QS0iLeFbvtQI((e-GTqm1LTc^+Ec9T~8_ zSr?a}xXjPC0y=cMV#-TaA{p{~di%+f`C2KL@R$YI1$sCOw2$iER z;U$`oq7(!=zT|=X+{)w53m_oCUvH|(g;2Z3!mHTzdPU~*gU&4`W<~Aw=xLVuqbi&B zP`f@9vt^U%$i!q@^I3`UG}`>P_lDJcDvcT8{JqO3J__R6NFXM6d$<_D1e15WDb1s` z+Zw;I>K5)Ds+q^8%Iw#9r*jX>+f1<++EI?2Rc;rKZ4t5DyApW|wKY7TeUDLii&Cvy zi(>9!8K46L@&J{*(>EeS#!Zwh08yhiwww*!r`CF zZa`Z~W7lC*P@gEO^xcbEbW3`3Q(?{+3{3F>V|@s*=kkp<|6RxL9^PdvGss-XhNr`2 zH^LAVikkm-SMzthK5FNvY)$PQ5G&RBkz z1n3{PmFVD;v}G2sM5{iW_-ltzKO);5jqP6n;{R*k_iO$Gbo+06m07>X{KveYb$eSs z^m|Y3kHw9{b+EFxGlF_|d-tOmU!M{)JQPo>Uyo8VDMYzsv)&YS8KSuyVsz|6YRL}C z=0-tMAldPBF>7IkjXpW#X32>3f>N~Q5W2!Sk7^aCWFC&*@o#l#rO~LNX+Mb#5|-jT z$gK=1D;PZ%g}pLG!t7X&a3&_p<}KZS(#Ln{!C|aAAZd2tl--3Wam%s0f=6anl4*To+ z{kwiRf`A|1KU3oR32*hU!9YAd*s&}2PU)cz$_BJ&NNoHH+Z$L`j2`Q$W6|?fI;FM# z_7-j;L(+p9c!h5~g-sGSTZ)@tQ-rsD4YyzMf9^>vlC&Dsug&JMjA!6DuF=T)`yBdj zuRPl~_4p&r{m1r&!{X34Itz7RcKq05ti%g=$!iJ_LK_$jke?0UVGB^E)#Zct>gshe5ew|%8tv{|5TGoI>!Xth~_uv&ADipv8 z!32va6qA;c#cE%OvWi<{hJ6~5O;ijg#=1~4hEyY2gQV<}G#XcI(PfeeMg11*BH%KR zds<|JQN7}YNP zhCjm9C|!I`1vON%D)3Ctk#L>_%nqfi&xBvtJodRFlAS9#ha~Ps<{&l5U|)!IsP>;1 zddkJ^dqzTm6B7RyLYLf(rA zb)Q2E=Y|S_SKXTx281sJh$Wv5Ne>zU1X7_?2-{oRst6Fz$CjaV} zf>;Z!*n^vW#l0Wd#qHB$S+?PAsB@Gz)4$DHQU)bYn_)L}oNwIazDjo#fXN{s`jOD% zb6f%L7j}f?_W_FC|0Vx!fLBlVagb?wY>DUeFcg&_bfo)7Fd5hq`5T2j(o$Jsb&kr6 zawSmGp>1)6(IPve5hT^t0&t|#7MyuoNvXFQ@)CvT9f$P+cc-vHvzw|Z8_!EI>*19ZQbUp&{pFJ}1^%v7_34llO z0daKvUTj6F=M3~3=L3yXQmWgNK2(Zs|0PhP*+ zrQvY^r94=c$vI8dLjC^Se{0o`s!wJYiq}OTlNw4Ar`9?mKMKWMo6sK*bcqVw1D0IT z+50S%G|}zA4DVsHXy@4X4g zd){o(7mCnP{b(d1S4|EiTxKOzd9GzpX)#3VfIRjRa)8cW;0WzgP~Tx?*gG-5Tdc1D zF5Z0!Xglso*6$=?(CdoXpsN(MGvp6)JN$v|f`U+tpAq}ckHe1m&dStLf*IGc)wfb7 z>|D!JI*c+b94By<(czgk!>KCo3QA_5;ZxC#qbUl;ZC~aQ2TdB}8m8*%swD?c!Tmj4 z*qI{1C+bI5KozP4&k+K8y}>CC}NDxKNS2t5kV1?@ zSE^E0iOxZp#c{&&N69j!VY)!OMS9u5OhLvNTM&qe_(SEZj+jHkq=3<&}U$O-LB0A&C zmIR|1D8X3H2qsEW@)A!0O43^%;Lr59D+lS7)qz(kmC(RfO!$2egW^-WG6pi~DJIM@ zMS#8>Q@fFk<9Vta>0B1n6d(i95VBy9$07nWBfc4qFm9f>p_^C=DWZTI!I!}Y!iG@7OPaaVUke}60WQ{bt`>rY?+QOCDb zBM^8#)%y3+1_)f8%NGuK`tRTD`3yMs2oxszu5y}+R?Pp#L;Bl45nQkL1j_r^$}D_y zv8Ls>cZnG9+dBSj@EEj;d_6o2_$Jb(*K;~h_n1PD*A$YRA-M!jBP%e0}ir$}n9 z!mjZ_aC~Fs_l+3NKZ!QDE(ueobtNP3dghmBs&a*|Dj>2=7pF}0f_|DaO{Ub<@CpW4Svuq2;K#NmS)C}F` z$>$)Tt`Sen;w=>Xh@24*=6$}Hnwt3paG-S|5La>Y*V_$f856E16X$-FfcUZT*wPYH zbNj4>Yl(Yxs<%|Js;A0rW-gr_BbWjJgrVGC|u}*;(DPa1(6} zDtHwgIpFX&g|Q8&t%ju>5wU&b$9z$T&PaDJr@iyWf<^kB82@wlvm26uBoxX&# z@NvO;GmsVp%Gv^yR~7a^ZVAoOuyz#`qZm9yO4<`28?vlh*Nr>H^zj|_;bq8%aCnMJ z22_%N9F?>lUga$!0`A6@PRf6*LQfSAIHd&8D8ATbheB0jc4YEZbzrqp470{qmrLQM zP2$94w+`5S*RD;JD$+syzM5CE?YUkdC6ES{vQ2>ehofxjLV9B z4>3*R2Zj{8YmLj6jnwq8Q;FC1Cbeid>ssvlq*gix+|zN$t)86`7($1G#3g+2sMH)F z`RN|e@P*j0Q7j)~Zs#g=nHej4GgXqHPzQRoh4-(zij+n3Z*k}G3uz^Oz1@gwZ1!MsA|TqRtmqy-k{X+VwZ!QfhBcx#?`Rc|JeI;R*QE&F4&LMC`9L`wR)UkQnMG5no za;{;3xvhnm{)9Xgyfh4g!v$vH#Y1kpL|ydl2LzgxT%i!Og@y$vto39QfZvmpm)hQ( zp1b)v9yRDNkYfa{T(7sn!E ziF?wb2`W|j@mBeI5ee)we%3SnUT6imOVH~cG}}m=clNE3ON00lZ_9&!EdRwq-h&+j znR=jy&=I-zn@_%1-sZHUVRSbTrAAQGG-Nmv+~i~q_r`!bhkc86N`$?uOKsr~qlq3C zDQj1ADOSHSn#%q9QlOW-nzF#0Z_y~ANUmi6qNByfR-j8;q+53WI(aPG%$*O2 z|NiFQB@*lC?|E?+9m{XTqAC9%z8ACfuO!C=4YzC>fl^DiP3A>eO_Phe3@Q9mkfw+| zy@QQe!O!@i<^9j?J;Q>{xUa}R?23PZf8G}kE!fSACqTqmX~AtF*o^#16~yK<8ws-g zcr#VmrhE+TJRI0U$lnk&{585XGIXE~3e7Q`wk8WdY?!533-TGxl{pNp0|bWNkNZAf z#*01MJ)Gx1?opR-U%j^VzWTvIJ3xR=*XPYv!EeCl@i!oJ$M61opjZ4hwVo`ie?H?A zaoC<0eM4X0KMNtl-Sl4kpeuH6#!BIeS7u7i$ zn!aQrV#bsKRPc1A4kK@Ra;Y)+2m+xs$hl;@1Ei*tuIPnZ17@FbKS}{(jjbVgDdU^Y zl47OP1h{Cb)5%@iE*zYSP+$OuM82*Y2%wzXFJ(}+Q;8x5wrktO5$G~I(fZ7_HG+(ZlzhP8*1YzF&e95*loc4STX zOIql0H^!%G%U|x*4=jANPa$aQdi(o~L3Lg4XFJG2%dJvHBrXhrBqJDo&Ln6tQ)2pi z$q30e$~;$O@Qg>jIZO1tIC5pMjuDy6RA(5TH1?+kv3boXh(}Qj_Z%sLVOW&$gxYs} zoa`R*eLooiQKEi$mrJZ<)Ddo~f6R3NHV+O?IYCxFI_L5|n-4Ra#lbk&@2Dt8!FAp3 zz=emi?p>NTpkUz7!@BO+PkZ0b)xys!$m;Fte;x*6J8og%;cFOhe=E!{>gh)nq-Id(4@&wetji4{^1zyq@Qe(T3dD88Dnw15Dbc2Rte5~i&6AN%EDivF*D-V7#fah`;kjgH)_ z%E{xm3iG=}GAa@ORdLaP;BBl)h0vzVBK-6_v!LwDco>!g4B57V*_xXGcz77wf8wQ~ zC)emkBnS!GeRyAIo05j^eI`~;7EFK5H+b4#S?=S!bnv3O+L@C{erh~i_X9^s^Py`| z_$y~am9cVlCW)9Nj9*P%LaTN=m%{38t%BLRc9F1l0&-g8FIM|e%CYR zSaTxyuMwwCgB}_v#R5;52#>uN~|&VWkQWIgSS6#-G`IEsT8c5ZF0PHY((=O12h&4w)MLymkMOq@@hTXc-ur3hcT7|q-M zwbCLBzYRG{-fF-!buT?^)NOa9-#D~X`V2&j*_N9VK={_MppI4O(v~P=gL0VP_&lKM zh{)n!Zr?+*oA^{QL#KyF^S1+fz23Fsa(;fNu&{XGVvY#ccK)}*Y6}j81MN8n3^OBX zFI$b2Zt9PSftI?KF#q+C9GlRrsKB-BQ$IoVp_Ow78Eb_q&da9Y#^uc|Y6U4)8Ca`s z7xeP%^cf%T`pq%L>Di^*=#Rxui9AaG;`BeCYW=z9{xO?mwL_Qd6?5SX9Ssg$NxLb^ zG@$^#ER=VUK(WFeN%UmJqvl4}PAZ$Mu2nAHFgvcUy;Hh%sTXQ&k{Fj4kGouR+Xq2~ zKH(J3{tHWo0FuSz-%HBxDOJjc$6(FyWaLFx`PHe%ieFqZXtc%8Zc3 z+G;D|%SyG(a(VV;jw1LUQ*3Oc(|xF^M=q~sdEmuo?F0;Yt~KLn?Yv~FjYQriT6q_3 zKow9KZHE1XkpzM04iaOqIUTJfF{&fBE9{(&hu6Y#gtm`|W-=YKh+a?sRnUR{-+VAv z;{l@6bEMa*g4)I(hg9$@nE}m+5Nb_?7YSVNgQ(#?dC|;O-JZS5IXc6itzt&woY_e) z*{q3I+KF9JxF>$WW=Bq5|9NQFe@5^SkH&Ni4p)yoHpHV5|Egt0$Xpo_(Y8`b15Grz zjleE8sznT(NGt5>Aw8(4OoA~BMi9Dd;!if%l^>Mh0aUZ2+03WJ+!z~$6D|}24njX1 zdHV5j0f$q(!%~C9!C}Wip(BNRBsJD>8%$or-7{#7U++}l9U0JJ7jws;;kqvht0iWC zytIycgZj!>M0kVOwBoUAwx_cYWgt)BqM+dcXhN1=C4o_HO(Kxbq=yjB{f&m*eGglEVhAX`=eB%*Ui`&5@i3sN=_mj04)7 zhf+5L2!5Bjq=35nvz}X`RwQ6m)z@IwDA6q3Po`mcw_e{8Rq|w@(w4*M?T?|7(n_Kc zU&ge)qWU@*vxBkqf&5uPAHuaPT-$9c=Vgm^Tp6j4f(}0+%w#n->&tKie&Tu%rMcJF zwe<(}l&aMv!B+};-W@Rx*y#8Dpp+nlJOD- zn)=@L$?<-ug}wvFK~UcT&P9;hxzf2;UsgsPmC0}Cv_5p^CW7b*iniOBEAdoR>_i*D z4wIzAdy&PmZSy2jtfPZ-9lJjQHOO~K?}b>-NyoQrEJSb+j2EjdrsQIKWcBpw^8v2$aNm2bGRI6EFB_^-+%n2Oojp@7eU<=HH|;T0!eHtx*ytFo$WGYcF4 zofyK#@mw|Zy~*8K!T9*U-6QY|(>epI;i^hRC{qM|p3=%E-XdLV5VVpfUJzDHw8AD5 zZiKl&7~9`w%8#EqUZr=_-yYhIFW976k;xoCf|h*t~9msDEN0P{~eKSi+$C_Hp zuSpW+@qeu@dS2y9d=KeU8VgmFL~A=2|8T3HQACePJr+tJfDIIkD%aqxq#fz#({3Fz zno149S`4dEE9%{n`+&}0tkAnns-;^=6TAmatCqi5jYJA!m^ql2A?GmQk{>8dvyPf+|N+z8A2= zu*86GAmOP0rBAR2uJFg`mpKmgDT22g8OiEjKTi;y<(w&2!Q*`VAm;6dWsqGH{Eq>~ zIrux_L@!N|HtJP=T9Dnj$``J3h_I?sJ|{0R5pVerV<15L>>Dm0VUKL2^mn-;b@1VO?Xsd68yjn9L)JGvl9uI4vVN3g$2dnP(OU@wu-WT62&H>b;XWr zZMD#G!~DI?p+#M9{_2vGyWrNKy|ygQ(Lhd!sU5wXXyAA!SNq70*pdL?BcNEF%v*8L zc{MzY!5p2ji@>2f-pcIQMyMMr>(#K(=|O_`wHQM={WPfjR!I8_KBScNN;w1+H=|Se z@Zv2gEqrY(;uqOb@(^aeHD+(-<(-i5jXKy}N*YaU*Az&xWQZYQm#R!ZzJ7OvQrp|H z%p0(IQ?`2D&ZBqrMN(Dzk-w$qrL;(y_z(Wy!&QvdxMz@{onu&XE`?kq(uenGwa`tZ zbx%(-qyONm4B@I~OL4`DxQYvf{qpi*w$~Hz2&xScs$TV)EF16*Iv@4LO$6Q_l8r15 z3-TXisjzeCIdbSO1wTQuDk2+M3iqr$yg0b0`-_I;1l9J&)z-Db+8~Kss!e(g3i}A@ z~VEH<^seSdX=qyd3lWHkHS2cM!DL)OE(8PKdqLu{ zw6%s^ksdaRbE{PA;D-OfxP4k~wQE#=VWsN;930iZ`U7Pr0+LZjKl6Nju-pC72>vpTwVyoN2tm3I`cvWuyqm9lcP2 zu$>UOP+BA&m~8Cg#UDme@!o5Yk!bZ%85+dx@n;Di{s*V&@V9x_1cc0|Curt(~2d zS;h!i=t1TY0yrhexv=?V5Z-crvl%k!|kNJ+QgP=pM}`D%?1@9?gR)K8I#X6Z-Oo@==oG zwz4w6&Xi}m6M)Hk$fx+D_eOS?N=E*NJ}FAV;N99w_^V?`dDYOql-lKbq-m$=<)V zB0&PoKD?>!1ogeQF1Bnpkq4Yw? zvV@fPA}7v+D|zuh-s7+Yf~vXe0No1jf2+<4Qh0S#&x9(F3xE_C|H-dR+;E;hmS8D6 z{QQ(J4PKpczZr2lCef4Va{Vse)$mU*$8ZRU-Mb?HLs1F0bSlZMkAFg6d*u>0jdE*h z6`opsKklf93ZuD>b5PYLG6>;qkq79P3zk@LNj6~3_l-5H{i1Xc>Uug(2D1opW9hzq zK@w_-zbIpaI(RC;ZG?Qn$p%qaC@+#>AMaJ^i{H!3Y)H0k(1N$QPOkpt^`;vP+NbOf zrw9_`#MOl&LmgnwJS0jYCMq27LNfLzZ-LmLf{v425Zq(S3$>9%P_`8>ueUi}^c{1A zYW94PN_?}y^0|B~t7BbJj#b>BD_7wpV<^F6c|~h&rb~CV0(K4l=AHgW-b6g7SjV9g z^RxAe<`-qG+?#VHdw-F3d$N7+-ZJ zH%E%%S39h3&!=(4vyBoG+T;GgZ!#D1)6oHSv0=p`8uMFN+v$ znGMeX@7wv!+{Uo={lg5nJ@H{DdgR8s0?y?HCwSuD$$gCgaIa@OsoGc(^L1PBp(PM^kUKWZOK)Yb z$J-NLrJczH@^z|6I|AU#722?8XWW^2G6Cn|zTy%sNkyf$%K~;)9?6VPz0W60Coy>a zp^vReSzNzQZGQM!IPfb`54;S+c1d?!h@OQh4WIf)h$j+tk3q2wV_DQ_U``ONG)E@t zv#jK" + debian_tar_name: "libqrtr-glib_1.4.0-1.debian.tar.xz" + debian_tar_url: "local:libqrtr-glib_1.4.0-1.debian.tar.xz" + debian_tar_sha256: "3feec39c03c29824a6ce30b3beff6188501e771abca44eff64d9a267f0598241" + dsc_checksums_sha256: | + b57068934577b0070c2f180f3dfcd115ce19efec10aeaf877b8a99c9226aaa2c 29521 libqrtr-glib_1.4.0.orig.tar.gz + 3feec39c03c29824a6ce30b3beff6188501e771abca44eff64d9a267f0598241 5084 libqrtr-glib_1.4.0-1.debian.tar.xz diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/changelog b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/changelog new file mode 100644 index 0000000..dda3e15 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/changelog @@ -0,0 +1,92 @@ +libqrtr-glib (1.4.0-1) unstable; urgency=medium + + [ Sebastien Bacher ] + * debian/tests: include a basic autopkgtest for the library + + [ Guido Günther ] + * New upstream release + * Switch to gi-docgen + + [ Arnaud Ferraris ] + * d/watch: drop custom matching pattern. + This was supposed to be clever (avoiding reporting dev versions), but it + actually reports a wrong version number, so let's just drop it and + revert back to default behaviour. + + -- Guido Günther Thu, 08 Jan 2026 18:55:34 +0100 + +libqrtr-glib (1.2.2-2) unstable; urgency=medium + + * d/gbp.conf: switch Debian branch to debian/latest. + Also add a few more defaults (copied from libqmi) for consistency. + * d/watch: convert to version 5. + Use the new Gitlab template for easier management, but override the + matching pattern so we only get stable (pre)releases, which have an even + minor version number. (Closes: #1081060) + * d/control: modernize build dependencies + `libgirepository1.0-dev` shouldn't be used anymore as it isn't + multiarch-friendly. Instead, use a recent `gobject-introspection` and + explicitly (build) depend on the needed `gir1.2-*-dev` packages. + Similarly, `pkg-config` has been deprecated in favor of `pkgconf`. + While at it, drop the now-obsolete `Rules-Requires-Root` and bump + `Standards-Version` as no other change is needed. (Closes: #1118881) + * debian: drop now-unneeded lintian overrides + * d/copyright: fix small issues. + One of the `meson.build` files is mentioned with a wrong path. Moreover, + the GPL-2+ text still mentions the (old) FSF street address, replace it + with the usual reference to http://www.gnu.org/licenses/. + + -- Arnaud Ferraris Tue, 28 Oct 2025 13:03:28 +0100 + +libqrtr-glib (1.2.2-1) unstable; urgency=medium + + * New upstream version 1.2.2 + * d/watch: watch for git tags instead of release tarballs. + Upstream doesn't publish release tarballs anymore, but instead relies + on git tags now. Update the `watch` file accordingly. + * debian: switch build system to meson. + Upstream dropped support for autotools-based builds starting with + version 1.2.0 in favor of meson. This requires the following changes on + our side: + - change configure flags + - modify build dependencies to depend on `meson` instead of + `autoconf-archive` + - add corresponding new copyright entries and update global project + license + While doing so, this commit also bumps the `glib2.0` minimum version + to reflect upstream changes and drops an unnecessary dependency on + `debhelper`. + * d/upstream: remove signing key. + With the move to git tags-based releases, tarballs are no longer signed. + Therefore we can drop our copy of the upstream signing key. + * d/libqrtr-glib-dev: don't install static library. + It isn't generated anymore. + * d/rules: drop obsolete override. + With the build system change, overriding `dh_missing` isn't needed + anymore: the files we used to exclude are no longer created. + * d/libqrtr-glib-doc: rename README and add lintian override. + Reflect the renaming of `README` to `README.md`. + Moreover, documentation generated by `gtk-doc` is installed to + `/usr/share/gtk-doc` as expected. Add a corresponding lintian override, + otherwise it will complain about docs installed outside + `/usr/share/doc`. + + -- Arnaud Ferraris Sat, 19 Feb 2022 14:33:19 +0100 + +libqrtr-glib (1.0.0-2) unstable; urgency=medium + + [ Federico Ceratto ] + * Use secure copyright file specification URI. + * Add missing build dependency on dh addon. + * Set upstream metadata fields: Repository. + + [ Evangelos Ribeiro Tzaras ] + * d/copyright: Mention docs under GFDL-1.3+ + + -- Arnaud Ferraris Wed, 02 Feb 2022 20:07:22 +0100 + +libqrtr-glib (1.0.0-1) unstable; urgency=medium + + * Initial Debian packaging (Closes: #985113) + + -- Arnaud Ferraris Thu, 04 Nov 2021 17:17:49 +0100 diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/control b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/control new file mode 100644 index 0000000..155591c --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/control @@ -0,0 +1,69 @@ +Source: libqrtr-glib +Priority: optional +Maintainer: DebianOnMobile Maintainers +Uploaders: Arnaud Ferraris , + Guido Günther , + Henry-Nicolas Tourneur , + Martin +Build-Depends: debhelper-compat (= 13), + dh-sequence-gir, + gir1.2-gio-2.0-dev, + gir1.2-gobject-2.0-dev, + gobject-introspection (>= 1.80), + libglib2.0-dev (>= 2.56), + meson, + pkgconf, + python3:any, +Build-Depends-Indep: gi-docgen , + libglib2.0-doc +Standards-Version: 4.7.2 +Section: libs +Homepage: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/ +Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib.git +Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib/ + +Package: libqrtr-glib0 +Architecture: linux-any +Multi-Arch: same +Depends: ${misc:Depends}, + ${shlibs:Depends}, +Description: Support library to use the QRTR protocol + glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. + . + This package contains the shared library. + +Package: libqrtr-glib-dev +Section: libdevel +Architecture: linux-any +Multi-Arch: same +Depends: libglib2.0-dev, + libqrtr-glib0 (= ${binary:Version}), + ${gir:Depends}, + ${misc:Depends}, +Provides: ${gir:Provides} +Suggests: libqrtr-glib-doc, +Description: Development files for libqrtr-glib + glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. + . + This package contains the library headers and development files. + +Package: libqrtr-glib-doc +Section: doc +Architecture: all +Multi-Arch: foreign +Depends: ${misc:Depends}, +Build-Profiles: +Description: API documentation for libqrtr-glib + glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. + . + This package contains the API documentation. + +Package: gir1.2-qrtr-1.0 +Section: introspection +Architecture: linux-any +Multi-Arch: same +Depends: ${gir:Depends}, ${misc:Depends} +Description: GObject introspection data for libqrtr-glib + glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. + . + This package contains the GObject-introspection data in binary typelib format. diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/copyright b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/copyright new file mode 100644 index 0000000..18e201e --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/copyright @@ -0,0 +1,65 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: libqrtr-glib +Upstream-Contact: Aleksander Morgado +Source: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/ + +Files: * +Copyright: 2019-2021 Eric Caruso + 2019-2020 Andrew Lassalle + 2020-2021 Aleksander Morgado +License: LGPL-2.1+ + +Files: debian/* +Copyright: 2012 Marius B. Kotsbak + 2021-2022 Arnaud Ferraris +License: GPL-2+ + +Files: meson.build +Copyright: 2021 Iñigo Martinez + 2021 Aleksander Morgado +License: LGPL-2.1+ + +Files: meson_options.txt + docs/reference/libqrtr-glib/meson.build + src/libqrtr-glib/meson.build +Copyright: 2021 Iñigo Martinez +License: LGPL-2.1+ + +License: LGPL-2.1+ + This package is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in "/usr/share/common-licenses/LGPL-2.1". + +License: GPL-2+ + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + . + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + . + You should have received a copy of the GNU General Public + License along with this package. If not, see + . + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. + diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gbp.conf b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gbp.conf new file mode 100644 index 0000000..2b1fbcd --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gbp.conf @@ -0,0 +1,11 @@ +[DEFAULT] +pristine-tar = True +debian-branch = debian/latest +upstream-branch = upstream/latest +upstream-vcs-tag=%(version)s + +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit + +[dch] +multimaint-merge = True diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gir1.2-qrtr-1.0.install b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gir1.2-qrtr-1.0.install new file mode 100644 index 0000000..9038727 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/gir1.2-qrtr-1.0.install @@ -0,0 +1 @@ +usr/lib/*/girepository-1.0/ diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-dev.install b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-dev.install new file mode 100644 index 0000000..63c6efd --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-dev.install @@ -0,0 +1,4 @@ +usr/include/* +usr/lib/*/lib*.so +usr/lib/*/pkgconfig/* +usr/share/gir-1.0/ diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.doc-base b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.doc-base new file mode 100644 index 0000000..aed92fb --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.doc-base @@ -0,0 +1,9 @@ +Document: libqrtr-glib +Title: Debian libqrtr-glib Manual +Author: The libqrtr-glib authors +Abstract: API documentation of libqrtr-glib. +Section: Programming/C + +Format: HTML +Index: /usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0/index.html +Files: /usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0/*.html diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.install b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.install new file mode 100644 index 0000000..5ea80da --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.install @@ -0,0 +1 @@ +usr/share/doc/libqrtr-glib-1.0 usr/share/doc/libqrtr-glib-doc/ diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.links b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.links new file mode 100644 index 0000000..28a4555 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib-doc.links @@ -0,0 +1,2 @@ +usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0 usr/share/devhelp/books/libqrtr-glib-1.0 + diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.docs b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.docs new file mode 100644 index 0000000..46a4ca7 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.docs @@ -0,0 +1,3 @@ +AUTHORS +NEWS +README.md diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.install b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.install new file mode 100644 index 0000000..3ddde58 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.install @@ -0,0 +1 @@ +usr/lib/*/lib*.so.* diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.symbols b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.symbols new file mode 100644 index 0000000..c7a9a7b --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/libqrtr-glib0.symbols @@ -0,0 +1,35 @@ +libqrtr-glib.so.0 libqrtr-glib0 #MINVER# +* Build-Depends-Package: libqrtr-glib-dev + qrtr_bus_get_node@Base 1.0.0 + qrtr_bus_get_nodes@Base 1.0.0 + qrtr_bus_get_type@Base 1.0.0 + qrtr_bus_new@Base 1.0.0 + qrtr_bus_new_finish@Base 1.0.0 + qrtr_bus_peek_node@Base 1.0.0 + qrtr_bus_peek_nodes@Base 1.0.0 + qrtr_bus_wait_for_node@Base 1.0.0 + qrtr_bus_wait_for_node_finish@Base 1.0.0 + qrtr_client_get_node@Base 1.0.0 + qrtr_client_get_port@Base 1.0.0 + qrtr_client_get_type@Base 1.0.0 + qrtr_client_new@Base 1.0.0 + qrtr_client_peek_node@Base 1.0.0 + qrtr_client_send@Base 1.0.0 + qrtr_get_node_for_uri@Base 1.0.0 + qrtr_get_uri_for_node@Base 1.0.0 + qrtr_node_get_bus@Base 1.0.0 + qrtr_node_get_id@Base 1.0.0 + qrtr_node_get_service_info_list@Base 1.0.0 + qrtr_node_get_type@Base 1.0.0 + qrtr_node_lookup_port@Base 1.0.0 + qrtr_node_lookup_service@Base 1.0.0 + qrtr_node_peek_bus@Base 1.0.0 + qrtr_node_peek_service_info_list@Base 1.0.0 + qrtr_node_service_info_free@Base 1.0.0 + qrtr_node_service_info_get_instance@Base 1.0.0 + qrtr_node_service_info_get_port@Base 1.0.0 + qrtr_node_service_info_get_service@Base 1.0.0 + qrtr_node_service_info_get_type@Base 1.0.0 + qrtr_node_service_info_get_version@Base 1.0.0 + qrtr_node_wait_for_services@Base 1.0.0 + qrtr_node_wait_for_services_finish@Base 1.0.0 diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/rules b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/rules new file mode 100644 index 0000000..1c151b0 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/rules @@ -0,0 +1,11 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +%: + dh $@ + +override_dh_auto_configure: + dh_auto_configure -- \ + -Dgtk_doc=$(if $(filter %-doc,$(shell dh_listpackages)),true,false) diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/salsa-ci.yml b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/salsa-ci.yml new file mode 100644 index 0000000..33c3a64 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/salsa-ci.yml @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/source/format b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/build b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/build new file mode 100644 index 0000000..d453c18 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/build @@ -0,0 +1,31 @@ +#!/bin/sh +# autopkgtest check: Build and run a program against libqrtr-glib, to verify that +# the headers and pkg-config file are installed correctly + +set -e + +if [ -n "${DEB_HOST_GNU_TYPE:-}" ]; then + CROSS_COMPILE="$DEB_HOST_GNU_TYPE-" +else + CROSS_COMPILE= +fi + +cat < libqrtr-glib_test.c +#include +#include + +int main(void) +{ + printf("The micro version is %d\n", QRTR_MICRO_VERSION); + return 0; +} +EOF + +# deliberately word-splitting pkg-config output: +# shellcheck disable=SC2046 +"${CROSS_COMPILE}gcc" -o libqrtr-glib_test libqrtr-glib_test.c \ +$("${CROSS_COMPILE}pkg-config" --cflags --libs qrtr-glib) +echo "build: OK" +[ -x libqrtr-glib_test ] +./libqrtr-glib_test +echo "run: OK" diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/control b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/control new file mode 100644 index 0000000..d836a36 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/tests/control @@ -0,0 +1,3 @@ +Tests: build +Depends: build-essential, pkg-config, libqrtr-glib-dev +Restrictions: allow-stderr superficial diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/upstream/metadata b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/upstream/metadata new file mode 100644 index 0000000..28f08aa --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/upstream/metadata @@ -0,0 +1,5 @@ +--- +Bug-Database: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/issues/ +Bug-Submit: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/issues/new +Repository: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib.git +Repository-Browse: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib diff --git a/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/watch b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/watch new file mode 100644 index 0000000..3776ff3 --- /dev/null +++ b/packaging/ci/fixtures/altered-salsa-tree/salsa-tree/debian/watch @@ -0,0 +1,4 @@ +Version: 5 + +Template: Gitlab +Dist: https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@ diff --git a/packaging/ci/test-verify-upstream-pins.sh b/packaging/ci/test-verify-upstream-pins.sh index d10d1c5..87f95db 100755 --- a/packaging/ci/test-verify-upstream-pins.sh +++ b/packaging/ci/test-verify-upstream-pins.sh @@ -1,12 +1,17 @@ #!/usr/bin/env bash # test-verify-upstream-pins.sh — executable proof that verify-upstream-pins.sh FAILS CLOSED. # -# Runs the three negative fixtures under ci/fixtures/ and asserts that each makes the verifier +# Runs the four negative fixtures under ci/fixtures/ and asserts that each makes the verifier # exit non-zero AND print the correct NAMED failing field on stderr: # -# wrong-signer -> FAIL [modemmanager] signer_fingerprint: signer mismatch … -# altered-dsc -> FAIL [modemmanager] dsc_signature: signature invalid (BADSIG) … -# altered-orig -> FAIL [modemmanager] orig_tar_sha256: checksum mismatch … +# wrong-signer -> FAIL [modemmanager] signer_fingerprint: signer mismatch … +# altered-dsc -> FAIL [modemmanager] dsc_signature: signature invalid (BADSIG) … +# altered-orig -> FAIL [modemmanager] orig_tar_sha256: checksum mismatch … +# altered-salsa-tree -> FAIL [libqrtr-glib] debian_tree: salsa vs .debian.tar.xz differ … +# +# The first three trip a link-1/2/3 tamper; altered-salsa-tree passes links 1–3 and trips the +# 4th (PACKAGING) link — a hash-valid .debian.tar.xz whose debian/ tree diverges (a dropped +# executable bit on debian/rules) from the fixture's local salsa comparison tree. # # Fixtures are OFFLINE: they use `local:` URLs (files packaged next to each fixture manifest) # and are run with --no-lineage, so this test needs no network and isolates the one tamper it @@ -21,20 +26,22 @@ VERIFY="$HERE/verify-upstream-pins.sh" rc=0 -# assert_reject +# assert_reject [source-name] # Runs the verifier on /pins.yaml and requires: non-zero exit, a -# `FAIL [..] :` line, and somewhere in stderr. +# `FAIL [] :` line, and somewhere in stderr. +# defaults to modemmanager (the source keyed by the first three fixtures); +# the tree-equality fixture keys its stanza `libqrtr-glib`, so it passes that name. assert_reject() { - local dir="$1" field="$2" needle="$3" + local dir="$1" field="$2" needle="$3" src="${4:-modemmanager}" local manifest="$FIX/$dir/pins.yaml" out ec - out="$(bash "$VERIFY" --no-lineage --source modemmanager --keys-base "$PKG_ROOT" "$manifest" 2>&1)" + out="$(bash "$VERIFY" --no-lineage --source "$src" --keys-base "$PKG_ROOT" "$manifest" 2>&1)" ec=$? if [ "$ec" -eq 0 ]; then echo "FAIL: '$dir' unexpectedly PASSED (exit 0) — fail-closed broken" rc=1 return fi - if ! printf '%s\n' "$out" | grep -q "FAIL \[modemmanager\] $field:"; then + if ! printf '%s\n' "$out" | grep -q "FAIL \[$src\] $field:"; then echo "FAIL: '$dir' rejected but not on field '$field'; got:" printf ' %s\n' "$out" | tail -1 rc=1 @@ -45,15 +52,16 @@ assert_reject() { rc=1 return fi - echo "ok: REJECT '$dir' (exit $ec) -> $(printf '%s\n' "$out" | grep -o "FAIL \[modemmanager\] $field:.*" | sed 's/^FAIL \[modemmanager\] //')" + echo "ok: REJECT '$dir' (exit $ec) -> $(printf '%s\n' "$out" | grep -o "FAIL \[$src\] $field:.*" | sed "s/^FAIL \[$src\] //")" } -assert_reject wrong-signer signer_fingerprint "signer mismatch" -assert_reject altered-dsc dsc_signature "signature invalid" -assert_reject altered-orig orig_tar_sha256 "checksum mismatch" +assert_reject wrong-signer signer_fingerprint "signer mismatch" +assert_reject altered-dsc dsc_signature "signature invalid" +assert_reject altered-orig orig_tar_sha256 "checksum mismatch" +assert_reject altered-salsa-tree debian_tree "executable-bit" libqrtr-glib if [ "$rc" -eq 0 ]; then - echo "PASS: verify-upstream-pins fails closed on wrong-signer / altered-.dsc / altered-.orig.tar" + echo "PASS: verify-upstream-pins fails closed on wrong-signer / altered-.dsc / altered-.orig.tar / altered-salsa-tree" else echo "FAIL: fail-closed contract violated" fi diff --git a/packaging/ci/verify-upstream-pins.sh b/packaging/ci/verify-upstream-pins.sh index 9257add..7c18fc1 100755 --- a/packaging/ci/verify-upstream-pins.sh +++ b/packaging/ci/verify-upstream-pins.sh @@ -12,7 +12,14 @@ # `.dsc` is the authority for the tarball checksums it embeds. # 3. ARTIFACT — the `.orig.tar` downloads and its sha256 equals `orig_tar_sha256`, which # equals the matching line in the `.dsc`'s Checksums-Sha256 (copied verbatim -# into the manifest's `dsc_checksums_sha256`). Chain closed. +# into the manifest's `dsc_checksums_sha256`). +# 4. PACKAGING — the `.debian.tar.xz` downloads (from `debian_tar_url`), its sha256 equals +# `debian_tar_sha256` (which equals its line in the verified `.dsc`, so the +# .dsc stays the checksum authority), and its extracted `debian/` tree is +# proven byte-identical to the pinned salsa tag's `debian/` tree via a +# CANONICAL TREE MANIFEST (per entry: relative path, file type, executable +# bit, symlink target, content sha256). Stronger than `diff -r` — it also +# catches exec-bit and symlink-target drift. Chain closed. # # ALL GPG work happens in a throwaway isolated GNUPGHOME (mktemp -d, 0700, rm -rf on exit). # The caller's ~/.gnupg is never touched or read. @@ -62,7 +69,7 @@ MANIFEST="$(cd "$(dirname "$MANIFEST")" && pwd)/$(basename "$MANIFEST")" MANIFEST_DIR="$(dirname "$MANIFEST")" [ -n "$KEYS_BASE" ] || KEYS_BASE="$MANIFEST_DIR" -for tool in gpg curl sha256sum awk git; do +for tool in gpg curl sha256sum awk git tar; do command -v "$tool" >/dev/null 2>&1 || { echo "verify-upstream-pins: missing required tool '$tool'" >&2; exit 2; } done @@ -218,7 +225,29 @@ verify_source() { salsa fi - echo "ok [$src] lineage + .dsc signature + checksums + .orig.tar verified" + # --- (4th link authority) pinned debian_tar_sha256 == the verified .dsc's own line --- + local deb_name deb_url deb_sha256 dsc_deb_sha + deb_name="$(yaml_scalar "$src" debian_tar_name)" + deb_url="$(yaml_scalar "$src" debian_tar_url)" + deb_sha256="$(yaml_scalar "$src" debian_tar_sha256)" + [ -n "$deb_name" ] || fail "$src" debian_tar_name "missing debian_tar_name in manifest" + dsc_deb_sha="$(printf '%s\n' "$dsc_ck" | awk -v f="$deb_name" '$3==f {print tolower($1); exit}')" + [ -n "$dsc_deb_sha" ] \ + || fail "$src" debian_tar_name "$deb_name not listed in the .dsc's Checksums-Sha256" + [ "$dsc_deb_sha" = "${deb_sha256,,}" ] \ + || fail "$src" debian_tar_sha256 "pin ($deb_sha256) != .dsc checksum ($dsc_deb_sha) for $deb_name" + + # --- (PACKAGING) download the .debian.tar.xz, hash it, then prove its debian/ tree is + # byte-identical to the pinned salsa tag's debian/ tree (canonical manifest) --- + local deb="$WORKDIR/$deb_name" + fetch "$deb_url" "$deb" || fail "$src" debian_tar_url "could not fetch $deb_url" + local got_deb; got_deb="$(sha256sum "$deb" | awk '{print $1}')" + [ "$got_deb" = "$deb_sha256" ] \ + || fail "$src" debian_tar_sha256 "checksum mismatch — expected $deb_sha256, got $got_deb" + verify_packaging_tree "$src" "$deb" \ + "$(yaml_scalar "$src" salsa_repo)" "$(yaml_scalar "$src" salsa_tag)" + + echo "ok [$src] lineage + .dsc signature + checksums + .orig.tar + debian/ tree verified" } # git ls-remote a tag and confirm both the tag-object SHA and the peeled commit SHA. @@ -236,6 +265,84 @@ verify_lineage() { || fail "$src" "${which}_commit_sha" "$tag commit is $got_commit, pin expects $want_commit" } +# Canonical metadata manifest of a debian/ tree, one line per entry, tab-separated: +# +# Symlinks are tested before -d/-f (those follow links); exec bit + content hash let the +# compare catch mode/content drift that a filename-only listing would miss. +canon_tree_manifest() { + ( cd "$1" && find . -mindepth 1 \( -type f -o -type d -o -type l \) | LC_ALL=C sort | while IFS= read -r p; do + rel="${p#./}" + if [ -L "$p" ]; then + printf '%s\tsymlink\t-\t%s\t-\n' "$rel" "$(readlink "$p")" + elif [ -d "$p" ]; then + printf '%s\tdir\t-\t-\t-\n' "$rel" + elif [ -f "$p" ]; then + if [ -x "$p" ]; then x=1; else x=0; fi + printf '%s\tfile\t%s\t-\t%s\n' "$rel" "$x" "$(sha256sum "$p" | awk '{print $1}')" + fi + done ) +} + +# 4th link: prove the .debian.tar.xz's debian/ tree equals the pinned salsa tag's debian/ +# tree. The salsa tree comes from a shallow clone of salsa_tag (real run) or a `local:` dir +# (offline fixtures). Fails closed naming the first differing path AND which field diverged. +verify_packaging_tree() { + local src="$1" deb="$2" salsa_repo="$3" salsa_tag="$4" + + local deb_extract="$WORKDIR/debtree-$src" + rm -rf "$deb_extract"; mkdir -p "$deb_extract" + tar -C "$deb_extract" -xf "$deb" >/dev/null 2>&1 \ + || fail "$src" debian_tar_name "could not extract $(basename "$deb")" + [ -d "$deb_extract/debian" ] \ + || fail "$src" debian_tar_name "$(basename "$deb") has no top-level debian/ directory" + + local salsa_debian + case "$salsa_repo" in + local:*) + salsa_debian="$MANIFEST_DIR/${salsa_repo#local:}/debian" + [ -d "$salsa_debian" ] \ + || fail "$src" salsa_repo "local salsa tree has no debian/ at $salsa_debian" + ;; + *) + local salsa_clone="$WORKDIR/salsatree-$src" + rm -rf "$salsa_clone" + git clone --depth 1 --branch "$salsa_tag" --quiet "$salsa_repo" "$salsa_clone" >/dev/null 2>&1 \ + || fail "$src" salsa_tag "could not shallow-clone $salsa_tag from $salsa_repo" + salsa_debian="$salsa_clone/debian" + [ -d "$salsa_debian" ] \ + || fail "$src" salsa_tag "$salsa_tag checkout has no debian/ directory" + ;; + esac + + local salsa_man="$WORKDIR/man-salsa-$src.txt" deb_man="$WORKDIR/man-deb-$src.txt" + canon_tree_manifest "$salsa_debian" > "$salsa_man" + canon_tree_manifest "$deb_extract/debian" > "$deb_man" + + local diffout first + diffout="$(awk -F'\t' ' + FNR==NR { s[$1]=$0; next } + { + seen[$1]=1 + if (!($1 in s)) { print $1 "\textra-entry\tin .debian.tar.xz tree but not in salsa tree"; next } + split(s[$1], sf, "\t") + if (sf[2]!=$2) { print $1 "\tfile-type\tsalsa=" sf[2] " debtar=" $2; next } + if (sf[3]!=$3) { print $1 "\texecutable-bit\tsalsa=" sf[3] " debtar=" $3; next } + if (sf[4]!=$4) { print $1 "\tsymlink-target\tsalsa=" sf[4] " debtar=" $4; next } + if (sf[5]!=$5) { print $1 "\tcontent-sha256\tsalsa=" sf[5] " debtar=" $5; next } + } + END { for (p in s) if (!(p in seen)) print p "\tmissing-entry\tin salsa tree but not in .debian.tar.xz tree" } + ' "$salsa_man" "$deb_man" | LC_ALL=C sort)" + + first="${diffout%%$'\n'*}" + if [ -n "$first" ]; then + local dpath dfield ddetail + dpath="$(printf '%s' "$first" | cut -f1)" + dfield="$(printf '%s' "$first" | cut -f2)" + ddetail="$(printf '%s' "$first" | cut -f3-)" + fail "$src" debian_tree "salsa vs .debian.tar.xz differ at '$dpath' [$dfield]: $ddetail" + fi +} + # ---- main ---------------------------------------------------------------------------------- echo "verify-upstream-pins: manifest=$MANIFEST lineage=$([ "$DO_LINEAGE" -eq 1 ] && echo on || echo off)" echo " isolated GNUPGHOME=$GNUPGHOME" diff --git a/packaging/keys/README.md b/packaging/keys/README.md index d9de9ea..477958a 100644 --- a/packaging/keys/README.md +++ b/packaging/keys/README.md @@ -15,8 +15,18 @@ and the manifest maps each source to its key via the `signer_key_file` field. | File | Fingerprint | Owner (UID) | Signs | |------|-------------|-------------|-------| -| `63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc` | `63F6 CCDF 9622 9D09 286B 2AC3 25BF 8652 4AFC C1E3` | Guido Günther `` (DebianOnMobile team) | modemmanager 1.24.0-1, libmbim 1.32.0-1, libqmi 1.36.0-1 | -| `796DB393DC3FF40222B6EA22D3EBB5966BB99196.asc` | `796D B393 DC3F F402 22B6 EA22 D3EB B596 6BB9 9196` | Arnaud Ferraris `` (DebianOnMobile team) | libqrtr-glib 1.2.2-1 | +| `63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc` | `63F6 CCDF 9622 9D09 286B 2AC3 25BF 8652 4AFC C1E3` | Guido Günther `` (DebianOnMobile team) | libqmi 1.38.0-1, libqrtr-glib 1.4.0-1 | +| `796DB393DC3FF40222B6EA22D3EBB5966BB99196.asc` | `796D B393 DC3F F402 22B6 EA22 D3EB B596 6BB9 9196` | Arnaud Ferraris `` (DebianOnMobile team) | modemmanager 1.24.2-2, libmbim 1.34.0-1 | + +> **Signer swap at the 1.24.2 / 1.34.0 / 1.38.0 / 1.4.0 bump:** whichever DebianOnMobile DD +> sponsors an upload signs that revision's `.dsc`, so the per-source signer can change between +> Debian revisions. For the previous pins (1.24.0-1 / 1.32.0-1 / 1.36.0-1 / 1.2.2-1) Guido +> signed the first three and Arnaud signed libqrtr-glib; for the current bump the pairing is +> inverted (Arnaud → modemmanager + libmbim, Guido → libqmi + libqrtr-glib). Both keys were +> already pinned here, so **no new signer key was required** — only the `signer_fingerprint` +> mapping in `../upstream-pins.yaml` moved. Both `63F6CCDF…C1E3` (a signing subkey of primary +> `0DB3 9327 62F7 8E59 2F65 22AF BB5A 2C77 5841 22D3`) and `796DB393…9196` verify GOODSIG on +> their respective new `.dsc`s in the isolated keyring. ## Acquisition source (documented per the plan) @@ -50,8 +60,8 @@ The fingerprint for each source was read **from the signature on the real `.dsc` not assumed: ```sh -gpg --verify modemmanager_1.24.0-1.dsc # -> "using RSA key 63F6CCDF...C1E3" -gpg --verify libqrtr-glib_1.2.2-1.dsc # -> "using RSA key 796DB393...9196" +gpg --verify libqmi_1.38.0-1.dsc # -> "using RSA key 63F6CCDF...C1E3" (Guido) +gpg --verify modemmanager_1.24.2-2.dsc # -> "using RSA key 796DB393...9196" (Arnaud) ``` So the checked-in key matches the key that actually produced each signature. The @@ -59,10 +69,14 @@ verify script re-establishes this end-to-end: import key -> `gpg --verify` the downloaded `.dsc` -> confirm the reported signing key equals the pinned `signer_fingerprint`. -## Note on the Arnaud Ferraris key expiry +## Note on key expiry -`796DB393…9196` shows an expiry (visible in `gpg --list-keys`), but the -libqrtr-glib 1.2.2-1 `.dsc` signature was made while the key was valid, so -`gpg --verify` still reports **Good signature** and exits 0. GPG validates a -signature against the key's state *at signing time*, not at verification time; -later expiry does not retroactively invalidate a signature that was good when made. +`796DB393…9196` (Arnaud) carries a rolling expiry — the checked-in armored key is +currently valid through **2026-10-13**, and the modemmanager 1.24.2-2 / libmbim +1.34.0-1 `.dsc` signatures it made (dated 2025-11 / 2026-01) verify as **Good +signature** (`GOODSIG`, not `EXPKEYSIG`). Even if this key later lapses, GPG validates +a signature against the key's state *at signing time*, not at verification time, so a +signature that was good when made stays good; the verify script treats `EXPKEYSIG` +(good signature from a since-expired key) the same as `GOODSIG` for exactly this reason. +If a re-export is ever needed to refresh the expiry window, re-run the acquisition +commands above — the fingerprint (the pin) does not change. diff --git a/packaging/upstream-pins.yaml b/packaging/upstream-pins.yaml index 392016c..294fb11 100644 --- a/packaging/upstream-pins.yaml +++ b/packaging/upstream-pins.yaml @@ -6,8 +6,8 @@ # real, resolved data — never a placeholder. `packaging/ci/verify-upstream-pins.sh` # re-checks every field in an isolated GNUPGHOME and fails closed on any drift. # -# THE PROVENANCE CHAIN (three independent links, all re-verified by the script) -# 1. LINEAGE — the upstream git tag. `git ls-remote --tags ` +# THE PROVENANCE CHAIN (four independent links, all re-verified by the script) +# 1. LINEAGE — the upstream git tag. `git ls-remote --tags ` # must still resolve to the pinned `upstream_tag_sha` (annotated tag object) and # `upstream_commit_sha` (peeled commit). This authenticates *lineage* — which commit # the release names. Per the plan, the git tag is NEVER byte-compared to a git archive; @@ -16,10 +16,18 @@ # the pinned `signer_fingerprint`, whose armored public key is checked into # `packaging/keys/.asc` (acquisition documented in packaging/keys/README.md). # A verified .dsc is the authority for the tarball checksums it embeds. -# 3. ARTIFACT — the `.orig.tar`. It is downloaded and its sha256 must equal +# 3. ARTIFACT — the `.orig.tar`. It is downloaded and its sha256 must equal # `orig_tar_sha256`, which itself equals the corresponding line in the .dsc's # `Checksums-Sha256:` section (copied verbatim into `dsc_checksums_sha256` below). -# So: verified .dsc -> embedded checksum -> downloaded tarball hash. Chain closed. +# So: verified .dsc -> embedded checksum -> downloaded tarball hash. +# 4. PACKAGING — the `.debian.tar.xz`. Downloaded from `debian_tar_url` and sha256-verified +# against `debian_tar_sha256` (which equals its line in the verified .dsc's +# `Checksums-Sha256:`, so the .dsc remains the checksum authority — NOT independently +# computed). It is then extracted and a CANONICAL TREE MANIFEST of its `debian/` dir +# (per entry: relative path, file type, executable bit, symlink target, content sha256) +# is compared byte-for-byte against the SAME manifest built from the pinned `salsa_tag`'s +# `debian/` tree. Equal manifests prove the salsa packaging recipe CeraLive checks in +# (`packaging//debian/`) is exactly the tree the signed .dsc ships. Chain closed. # # WHY signer_fingerprint IS THE .dsc SIGNER (not the upstream tag signer) # The cryptographic authority for the *tarball checksums* is the Debian Developer who @@ -30,105 +38,128 @@ # objects (a heavier clone the plan tells us to avoid). Lineage = SHA-pin; tarball # authority = .dsc GPG signature. Two orthogonal guarantees, both enforced. # +# NOTE: the .dsc signer per source can change between Debian revisions — whichever +# DebianOnMobile DD sponsored the upload signs it. For this bump the pairing is +# modemmanager+libmbim -> Arnaud Ferraris, libqmi+libqrtr-glib -> Guido Günther (the +# inverse of the previous 1.24.0/1.32.0/1.36.0/1.2.2 pins). Both keys were already pinned; +# no new signer key was required. Each `signer_fingerprint` is the key that actually +# produced that revision's signature, read FROM the signature, not assumed. +# # EXECUTOR NOTE -# These are the tags NAMED by the plan (ModemManager 1.24.0, libmbim 1.32.0, libqmi 1.36.0, -# libqrtr-glib 1.2.2; salsa debian/-1). The executor resolves — never chooses — -# versions. All twelve tags (4 upstream annotated + 4 salsa annotated) were confirmed to -# exist via `git ls-remote` on 2026-07-14; every checksum below was computed from a real -# download and cross-checked against the GPG-verified .dsc. +# These are the tags NAMED by the plan (ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, +# libqrtr-glib 1.4.0; salsa debian/1.24.2-2, debian/1.34.0-1, debian/1.38.0-1, debian/1.4.0-1). +# The executor resolves — never chooses — versions. All twelve tags (4 upstream annotated + +# 4 salsa annotated) were confirmed to exist via `git ls-remote` on 2026-07-16; a +# no-newer-stable enumeration of each upstream's release tags confirmed none is newer than +# the pinned version (higher tags are all -dev / -rc / .995 pre-releases). Every checksum +# below was copied from the GPG-verified .dsc and cross-checked against a real download. +# The .debian.tar.xz `debian/` tree was proven byte-identical to the salsa tag's tree. +# +# Tarball compression note: the orig tarballs were RE-ROLLED for this bump — libmbim and +# libqmi moved .orig.tar.xz -> .orig.tar.gz, and libqrtr-glib moved .orig.tar.bz2 -> +# .orig.tar.gz. Only modemmanager stays .orig.tar.xz. `orig_tar_name` reflects the REAL +# name in each verified .dsc, not the previous convention. # # FILE FORMAT # Deliberately flat, 2-space-nested YAML with a single block literal per source # (`dsc_checksums_sha256`). Parsed by a dependency-free awk reader in the verify script, -# so the packaging CI container needs no python/yq — only bash+gpg+curl+sha256sum+git. +# so the packaging CI container needs no python/yq — only bash+gpg+curl+sha256sum+git+tar. schema_version: 1 sources: modemmanager: - upstream_tag: "1.24.0" + upstream_tag: "1.24.2" upstream_repo: "https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git" - upstream_tag_sha: "8e8dbf92f4e3be2aa9ae1b42edbbde275b3426c2" - upstream_commit_sha: "dfa41adf391b090720fb1ea56d884f61ea7fba29" - signer_fingerprint: "63F6CCDF96229D09286B2AC325BF86524AFCC1E3" - signer_key_file: "keys/63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc" - orig_tar_name: "modemmanager_1.24.0.orig.tar.xz" - orig_tar_url: "https://deb.debian.org/debian/pool/main/m/modemmanager/modemmanager_1.24.0.orig.tar.xz" - orig_tar_sha256: "63ded4c0f3936bb0db5ae35ef1dfd57c5d5b4dd8a5cdaa7fb2182255218c9168" + upstream_tag_sha: "68b1c1c442a34e0bbdfee2abd74c07470a9fa184" + upstream_commit_sha: "f2b9ab1ad78d322f32134a444b5b54c6e8160e19" + signer_fingerprint: "796DB393DC3FF40222B6EA22D3EBB5966BB99196" + signer_key_file: "keys/796DB393DC3FF40222B6EA22D3EBB5966BB99196.asc" + orig_tar_name: "modemmanager_1.24.2.orig.tar.xz" + orig_tar_url: "https://deb.debian.org/debian/pool/main/m/modemmanager/modemmanager_1.24.2.orig.tar.xz" + orig_tar_sha256: "8f575bfdcc0277b68946a65b527a804be8051abbb17430b6108da365a92c6913" salsa_repo: "https://salsa.debian.org/DebianOnMobile-team/modemmanager.git" - salsa_tag: "debian/1.24.0-1" - salsa_tag_sha: "92692e6a3f030dded827b74f29e45fa216e2a421" - salsa_commit_sha: "260e3c0fe1878929ab3f2d727f9ae47c69f729f1" - # 1.24.0-1 is superseded in the live pool (now 1.24.0-1+deb13u1), so the .dsc is pinned - # to snapshot.debian.org's permanent content-addressed URL (sha1 39e06def...). The - # verify script also derives this same snapshot URL from the .dsc's Checksums-Sha1 as a - # fallback for any source whose live-pool .dsc later 404s. - dsc_url: "https://snapshot.debian.org/file/39e06def35a111d2e69dc23e62e042fa389bada1" - dsc_sha256: "f2a0a21fdce6619bf874ab182385225c65b82d217d6b6ed20933ded5c375fa1c" - dsc_signer: "Guido Günther " + salsa_tag: "debian/1.24.2-2" + salsa_tag_sha: "c32cb34015be9738c2d2e3372c11f516d78c4fa6" + salsa_commit_sha: "56d7b2dba1f3ddbd99af9522a085fbb35ba06289" + dsc_url: "https://deb.debian.org/debian/pool/main/m/modemmanager/modemmanager_1.24.2-2.dsc" + dsc_sha256: "51f3682ceb1f0ba154107d9cf2f75218b332823be19302c875d8d6874f1694a2" + dsc_signer: "Arnaud Ferraris " + debian_tar_name: "modemmanager_1.24.2-2.debian.tar.xz" + debian_tar_url: "https://deb.debian.org/debian/pool/main/m/modemmanager/modemmanager_1.24.2-2.debian.tar.xz" + debian_tar_sha256: "5051465dffee001eb4c77ccfcb36e496ff793879f13a84cbb7484a673ed7e204" dsc_checksums_sha256: | - 63ded4c0f3936bb0db5ae35ef1dfd57c5d5b4dd8a5cdaa7fb2182255218c9168 1361836 modemmanager_1.24.0.orig.tar.xz - 7585e8cf6fb920e516372ed54d8f5c3d2faf1ffec920ec90fb550b9aaa11de12 36680 modemmanager_1.24.0-1.debian.tar.xz + 8f575bfdcc0277b68946a65b527a804be8051abbb17430b6108da365a92c6913 1366356 modemmanager_1.24.2.orig.tar.xz + 5051465dffee001eb4c77ccfcb36e496ff793879f13a84cbb7484a673ed7e204 43380 modemmanager_1.24.2-2.debian.tar.xz libmbim: - upstream_tag: "1.32.0" + upstream_tag: "1.34.0" upstream_repo: "https://gitlab.freedesktop.org/mobile-broadband/libmbim.git" - upstream_tag_sha: "4b75d820a8bbc3d1c44f8f6fb5b03720c6a59e0d" - upstream_commit_sha: "2bc1080292aae30000e808ec77d7a99b87eb3553" - signer_fingerprint: "63F6CCDF96229D09286B2AC325BF86524AFCC1E3" - signer_key_file: "keys/63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc" - orig_tar_name: "libmbim_1.32.0.orig.tar.xz" - orig_tar_url: "https://deb.debian.org/debian/pool/main/libm/libmbim/libmbim_1.32.0.orig.tar.xz" - orig_tar_sha256: "3d516f036f4ca486e2288a397b2c6a9f5d7242b274615f7224137ed37c5e4de7" + upstream_tag_sha: "008fd1117bf44419a2e809fcd4feabe5914077dd" + upstream_commit_sha: "2bca5d22137883f348cb117597746483637491db" + signer_fingerprint: "796DB393DC3FF40222B6EA22D3EBB5966BB99196" + signer_key_file: "keys/796DB393DC3FF40222B6EA22D3EBB5966BB99196.asc" + orig_tar_name: "libmbim_1.34.0.orig.tar.gz" + orig_tar_url: "https://deb.debian.org/debian/pool/main/libm/libmbim/libmbim_1.34.0.orig.tar.gz" + orig_tar_sha256: "ba6c1f316b108c0b59c920044a363b7fdcc87d41882b3dfd5a4ece7c003d112b" salsa_repo: "https://salsa.debian.org/DebianOnMobile-team/libmbim.git" - salsa_tag: "debian/1.32.0-1" - salsa_tag_sha: "db0652b434389552c98391afaa5107554dac012c" - salsa_commit_sha: "9a61539c6c5b2b57c4c8e47295080745e9f2b4b1" - dsc_url: "https://deb.debian.org/debian/pool/main/libm/libmbim/libmbim_1.32.0-1.dsc" - dsc_sha256: "6ea23929777aee45a1a076718281daee6add01bf29b0fd52cd652fc07558d8e9" - dsc_signer: "Guido Günther " + salsa_tag: "debian/1.34.0-1" + salsa_tag_sha: "c95aa88a715fc300d6a51d5a01c96adc8e78296d" + salsa_commit_sha: "c9b0205739e0ae119a9242ff5eab60111b5914c5" + dsc_url: "https://deb.debian.org/debian/pool/main/libm/libmbim/libmbim_1.34.0-1.dsc" + dsc_sha256: "5413fccb85631eeded506f0ba7ef33f9ad107162f74c1894d2952fb52f04bf03" + dsc_signer: "Arnaud Ferraris " + debian_tar_name: "libmbim_1.34.0-1.debian.tar.xz" + debian_tar_url: "https://deb.debian.org/debian/pool/main/libm/libmbim/libmbim_1.34.0-1.debian.tar.xz" + debian_tar_sha256: "3f89429565d1310ef25acf51427caf6f2782e2268cac8d9a384014219aadab39" dsc_checksums_sha256: | - 3d516f036f4ca486e2288a397b2c6a9f5d7242b274615f7224137ed37c5e4de7 216108 libmbim_1.32.0.orig.tar.xz - 6e314011c56e2fc8d6ce8ccb59f8cd9ae6b01909c86e9b24e21c55f7b6f26f74 15436 libmbim_1.32.0-1.debian.tar.xz + ba6c1f316b108c0b59c920044a363b7fdcc87d41882b3dfd5a4ece7c003d112b 299023 libmbim_1.34.0.orig.tar.gz + 3f89429565d1310ef25acf51427caf6f2782e2268cac8d9a384014219aadab39 15828 libmbim_1.34.0-1.debian.tar.xz libqmi: - upstream_tag: "1.36.0" + upstream_tag: "1.38.0" upstream_repo: "https://gitlab.freedesktop.org/mobile-broadband/libqmi.git" - upstream_tag_sha: "70f141dffbe766ff645811520e86506143a3769a" - upstream_commit_sha: "defb13dcab0adc7f44f6741807244507a14a30c5" + upstream_tag_sha: "5de022769c13a30706deed53185bddfc3c1e30a1" + upstream_commit_sha: "e3d79ed3f7fdc6a7fa8b374860b1c23a8f172812" signer_fingerprint: "63F6CCDF96229D09286B2AC325BF86524AFCC1E3" signer_key_file: "keys/63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc" - orig_tar_name: "libqmi_1.36.0.orig.tar.xz" - orig_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqmi/libqmi_1.36.0.orig.tar.xz" - orig_tar_sha256: "0b4c369f8d2d6ba59f19029a60f2b2449894ffd132297442d6d98791d372b557" + orig_tar_name: "libqmi_1.38.0.orig.tar.gz" + orig_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqmi/libqmi_1.38.0.orig.tar.gz" + orig_tar_sha256: "0bb02f08b8dac678540f865cac3cf1156e487aae1d3b350e1c45f03be651bb2e" salsa_repo: "https://salsa.debian.org/DebianOnMobile-team/libqmi.git" - salsa_tag: "debian/1.36.0-1" - salsa_tag_sha: "1b3fc7628fb9b00f0d93e73c41d7081fa61ce321" - salsa_commit_sha: "b76e958fa882e3d2f8438d222a4bbbcb68c3456b" - dsc_url: "https://deb.debian.org/debian/pool/main/libq/libqmi/libqmi_1.36.0-1.dsc" - dsc_sha256: "6476feadbb787007e24d1179fe0c280eba8dc0691f4ad1fb0f5dccc801ed0ba5" + salsa_tag: "debian/1.38.0-1" + salsa_tag_sha: "4b096a77c2d8a7e82172e0e863e25f93ab31800a" + salsa_commit_sha: "b8f602daeeee8347b6c710ac29c2d51a6449bf84" + dsc_url: "https://deb.debian.org/debian/pool/main/libq/libqmi/libqmi_1.38.0-1.dsc" + dsc_sha256: "0c94dbb7f05937762a2dc26a8c34f855a5065f9a42a14deabeb2dd24d0c04340" dsc_signer: "Guido Günther " + debian_tar_name: "libqmi_1.38.0-1.debian.tar.xz" + debian_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqmi/libqmi_1.38.0-1.debian.tar.xz" + debian_tar_sha256: "2a91645df461992da096ea3ae25f20c79be2d767cd3991b5e09ebed9348dbe67" dsc_checksums_sha256: | - 0b4c369f8d2d6ba59f19029a60f2b2449894ffd132297442d6d98791d372b557 1534688 libqmi_1.36.0.orig.tar.xz - 444e0c21f2cd7b4a7b51029a3cc6c63b42d83fa045778205e68a022e55a172a5 38672 libqmi_1.36.0-1.debian.tar.xz + 0bb02f08b8dac678540f865cac3cf1156e487aae1d3b350e1c45f03be651bb2e 4395875 libqmi_1.38.0.orig.tar.gz + 2a91645df461992da096ea3ae25f20c79be2d767cd3991b5e09ebed9348dbe67 47588 libqmi_1.38.0-1.debian.tar.xz libqrtr-glib: - upstream_tag: "1.2.2" + upstream_tag: "1.4.0" upstream_repo: "https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib.git" - upstream_tag_sha: "d11055a6a4bb7dd811325ee4531dbeaf1c58abf2" - upstream_commit_sha: "8991f0e93713ebf4da48ae4f23940ead42f64c8c" - signer_fingerprint: "796DB393DC3FF40222B6EA22D3EBB5966BB99196" - signer_key_file: "keys/796DB393DC3FF40222B6EA22D3EBB5966BB99196.asc" - orig_tar_name: "libqrtr-glib_1.2.2.orig.tar.bz2" - orig_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqrtr-glib/libqrtr-glib_1.2.2.orig.tar.bz2" - orig_tar_sha256: "69ec8883d887a426c6402440b200fa53c41e9da8e098333369f2388d3559e856" + upstream_tag_sha: "459c2042c24b21cb9f05ee0f318b5e6203a2e5c4" + upstream_commit_sha: "69791f8f2a092d0ec8eb83cbe51dfc3d157c3079" + signer_fingerprint: "63F6CCDF96229D09286B2AC325BF86524AFCC1E3" + signer_key_file: "keys/63F6CCDF96229D09286B2AC325BF86524AFCC1E3.asc" + orig_tar_name: "libqrtr-glib_1.4.0.orig.tar.gz" + orig_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqrtr-glib/libqrtr-glib_1.4.0.orig.tar.gz" + orig_tar_sha256: "b57068934577b0070c2f180f3dfcd115ce19efec10aeaf877b8a99c9226aaa2c" salsa_repo: "https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib.git" - salsa_tag: "debian/1.2.2-1" - salsa_tag_sha: "5cc942704905d1152ff8641398a1d8918ebf5d28" - salsa_commit_sha: "4933c7a281cd0c6c6ac79464b8a8876da0d93ef2" - dsc_url: "https://deb.debian.org/debian/pool/main/libq/libqrtr-glib/libqrtr-glib_1.2.2-1.dsc" - dsc_sha256: "4ef2af0a803ad6455bc7fe4b643493a7fca3a8fbc90cbfb22e7a8470b6e0d305" - dsc_signer: "Arnaud Ferraris " + salsa_tag: "debian/1.4.0-1" + salsa_tag_sha: "b886af45dfcc1356748f93fd6db0abe1bb2ac581" + salsa_commit_sha: "0b45abffcc9fafeb50ecbe9ac6f78b94559112ed" + dsc_url: "https://deb.debian.org/debian/pool/main/libq/libqrtr-glib/libqrtr-glib_1.4.0-1.dsc" + dsc_sha256: "736ff0cf0aeb5e78f5d8fcf2858733b7ad5a2d06647b83a50dfb6d3b03d01659" + dsc_signer: "Guido Günther " + debian_tar_name: "libqrtr-glib_1.4.0-1.debian.tar.xz" + debian_tar_url: "https://deb.debian.org/debian/pool/main/libq/libqrtr-glib/libqrtr-glib_1.4.0-1.debian.tar.xz" + debian_tar_sha256: "3feec39c03c29824a6ce30b3beff6188501e771abca44eff64d9a267f0598241" dsc_checksums_sha256: | - 69ec8883d887a426c6402440b200fa53c41e9da8e098333369f2388d3559e856 26398 libqrtr-glib_1.2.2.orig.tar.bz2 - d2722191761ce2fa5ddf8ff8c0dd7b2882a2935cdbe189ce4901e60a78670c48 4300 libqrtr-glib_1.2.2-1.debian.tar.xz + b57068934577b0070c2f180f3dfcd115ce19efec10aeaf877b8a99c9226aaa2c 29521 libqrtr-glib_1.4.0.orig.tar.gz + 3feec39c03c29824a6ce30b3beff6188501e771abca44eff64d9a267f0598241 5084 libqrtr-glib_1.4.0-1.debian.tar.xz From 737d912b496d9abb16c7d4f7fed644f0c5e7cf70 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Thu, 16 Jul 2026 23:49:43 -0500 Subject: [PATCH 02/14] packaging: refresh debian/ trees; bookworm adaptations incl. GI-1.74; frozen package sets + external-dep preflight --- packaging/BOOKWORM-ADAPTATIONS.md | 189 +++++-- packaging/ModemManager/debian/changelog | 33 ++ packaging/ModemManager/debian/control | 22 +- packaging/ModemManager/debian/copyright | 22 - packaging/ModemManager/debian/gbp.conf | 4 + .../debian/upstream/signing-key.asc | 135 +++++ packaging/ModemManager/debian/watch | 9 +- packaging/README.md | 8 +- packaging/ci/expected-packages.txt | 97 ++++ packaging/libmbim/debian/changelog | 30 ++ packaging/libmbim/debian/control | 15 +- packaging/libmbim/debian/gbp.conf | 8 +- .../libmbim/debian/libmbim-glib4.symbols | 5 + packaging/libmbim/debian/watch | 9 +- packaging/libqmi/debian/changelog | 51 ++ packaging/libqmi/debian/control | 14 +- packaging/libqmi/debian/copyright | 5 +- packaging/libqmi/debian/gbp.conf | 2 +- .../libqmi/debian/libqmi-glib-doc.doc-base | 4 +- .../libqmi/debian/libqmi-glib-doc.install | 2 +- packaging/libqmi/debian/libqmi-glib-doc.links | 2 +- packaging/libqmi/debian/libqmi-glib5.symbols | 485 ++++++++++++++++++ .../libqmi/debian/upstream/signing-key.asc | 135 +++++ packaging/libqmi/debian/watch | 9 +- packaging/libqrtr-glib/debian/changelog | 40 ++ packaging/libqrtr-glib/debian/control | 21 +- packaging/libqrtr-glib/debian/copyright | 24 +- packaging/libqrtr-glib/debian/gbp.conf | 9 +- .../debian/libqrtr-glib-doc.doc-base | 4 +- .../debian/libqrtr-glib-doc.install | 2 +- .../debian/libqrtr-glib-doc.links | 3 +- .../debian/libqrtr-glib-doc.lintian-overrides | 2 - packaging/libqrtr-glib/debian/rules | 11 +- packaging/libqrtr-glib/debian/source/options | 1 - packaging/libqrtr-glib/debian/tests/build | 31 ++ packaging/libqrtr-glib/debian/tests/control | 3 + packaging/libqrtr-glib/debian/watch | 7 +- 37 files changed, 1270 insertions(+), 183 deletions(-) create mode 100644 packaging/ci/expected-packages.txt delete mode 100644 packaging/libqrtr-glib/debian/libqrtr-glib-doc.lintian-overrides delete mode 100644 packaging/libqrtr-glib/debian/source/options create mode 100644 packaging/libqrtr-glib/debian/tests/build create mode 100644 packaging/libqrtr-glib/debian/tests/control diff --git a/packaging/BOOKWORM-ADAPTATIONS.md b/packaging/BOOKWORM-ADAPTATIONS.md index 7dceacd..c72793a 100644 --- a/packaging/BOOKWORM-ADAPTATIONS.md +++ b/packaging/BOOKWORM-ADAPTATIONS.md @@ -1,78 +1,153 @@ # Bookworm adaptations -The four sources are rebuilt from their **pinned trixie `debian/-1` packaging** +The four sources are rebuilt from their **pinned sid/trixie `debian/-` packaging** ([`upstream-pins.yaml`](upstream-pins.yaml) `salsa_commit_sha`) — **zero source patches** -(`debian/patches/` is empty for every source; see [`POLICY.md`](../POLICY.md)). The trixie +(`debian/patches/` is empty for every source; see [`POLICY.md`](../POLICY.md)). The sid packaging does not build unmodified on bookworm, so a small set of **packaging-metadata** -adaptations is applied. This file records every one of them and why. +adaptations is applied, limited to the pre-authorized salsa-deviation class: `debian/control` +build-dependency relaxations/substitutions and `debian/rules` meson/install-path pins that +mirror stock bookworm's own packaging of the same source. Nothing else — no maintainer-script +changes, no package add/remove/rename, no `Breaks`/`Replaces`/`Conflicts` edits, no symbols +regeneration, no `debian/patches/` content. This file records every adaptation and why. -Each `debian/` dir here is byte-identical to the pinned salsa commit **except** for the -hunks below. Reproduce the delta with `test-results/modem-control/A5.1/debdiff.txt` -(a recursive diff of each adapted `debian/` against the salsa tree at its pinned SHA). +Each `debian/` dir here is byte-identical to the pinned salsa commit **except** for the hunks +below. The delta is captured per source as a recursive diff against the raw salsa tree at its +pinned SHA — `test-results/upstream-currency/1.2/debdiff-.txt` (×4). Symbols files are +**byte-preserved** from the salsa tree (verified sha256-identical); any need to regenerate a +symbols file is a HARD STOP, not a local fix. -## libqrtr-glib, libmbim, libqmi — zero adaptations +**Bases (this bump):** ModemManager `1.24.2-2`, libmbim `1.34.0-1`, libqmi `1.38.0-1`, +libqrtr-glib `1.4.0-1`. All four adaptation sets were **re-derived** against these NEW bases +(the prior derivation targeted the older `1.24.0-1`/`1.32.0`/`1.36.0`/`1.2.2` packaging). -These three build unmodified on bookworm. Their `debian/` dirs are pristine copies of the -pinned salsa commits. Their only cross-source build-deps (`libqmi` → `libqrtr-glib-dev`, -`libmbim-glib-dev`) are satisfied by the freshly built packages in the temporary local apt -repo — that is what the bootstrap build order exists for, not a packaging change. +## The GObject-introspection adaptation — ALL FOUR sources (NEW this bump) -## ModemManager — three adaptations (all in `debian/control` + `debian/rules`) +The single new, cross-cutting adaptation. Between the previously-pinned revisions and these, +the DebianOnMobile maintainers **modernized the introspection build-deps** on every source: +they dropped `libgirepository1.0-dev` and switched to a split, multiarch-friendly set — +`gobject-introspection (>= 1.80)` plus explicit `gir1.2--2.0-dev` packages. The changelog +rationale is explicit and identical across sources, e.g. libmbim `1.32.0-2` / MM `1.24.2-2`: -The trixie `modemmanager 1.24.0-1` packaging assumes a **trixie** build environment. Three -things differ on bookworm; each adaptation is the minimal, bookworm-native fix. +> `libgirepository1.0-dev` shouldn't be used anymore as it isn't multiarch-friendly. Instead, +> use a recent `gobject-introspection` and explicitly (build) depend on the needed +> `gir1.2-*-dev` packages. (MM: Closes #1118899 / #1087277 "Improve cross building".) -### 1. debhelper version relax (documented) — `debian/control` +That switch is a **Debian archive/cross-build policy** choice, **not** an upstream feature +requirement. On bookworm it is unsatisfiable two ways: -`debhelper (>= 13.11.6)` → `debhelper (>= 13.11.4)`. Bookworm ships debhelper **13.11.4**; -the trixie packaging pinned `>= 13.11.6`, which is unsatisfiable on bookworm. Compat level -is unchanged (`debhelper-compat (= 13)`, provided by bookworm's debhelper). Nothing the -build needs from 13.11.6 is used. +- `gobject-introspection` in bookworm is **1.74.0-3** — `(>= 1.80)` cannot be met. +- the split `gir1.2-gio-2.0-dev` / `gir1.2-glib-2.0-dev` / `gir1.2-gobject-2.0-dev` packages + **do not exist** in bookworm at all (they are a GI-1.80-era packaging split). -### 2. `systemd-dev` → `udev` build-dep (documented) — `debian/control` +### Adaptation — `debian/control` (each source) -`systemd-dev` is a **trixie/forky-only** package that does not exist in bookworm. In trixie -it ships **both** `udev.pc` (for the udev base dir) **and** `systemd.pc` (for the systemd -system-unit dir). On bookworm those two `.pc` files are split across separate packages: -`udev.pc` is in `udev`, `systemd.pc` is in `systemd`. The substitution restores the `udev.pc` -half. `libsystemd-dev (>= 209)` is kept unchanged (it links libsystemd for journal / +Replace the sid GI-1.80 build-dep block with bookworm's GI-1.74 equivalent: + +``` +- gir1.2-gio-2.0-dev, +- gir1.2-glib-2.0-dev, (MM + libmbim only; libqmi/libqrtr-glib omit it) +- gir1.2-gobject-2.0-dev, +- gobject-introspection (>= 1.80), ++ gobject-introspection, ++ libgirepository1.0-dev, +``` + +Nothing else in any `control` is touched by this adaptation; the doc tooling each source now +uses (`gi-docgen` for libqmi/libqrtr-glib, `gtk-doc-tools` for MM/libmbim) and `pkgconf` are +all present in bookworm and are left exactly as upstream ships them. + +### Why GI 1.74 is sufficient (empirical, not assumed) + +- **Upstream floor.** All four `meson.build` files gate introspection on + `dependency('gobject-introspection-1.0', version: '>= 0.9.6')` (MM l.295, libmbim l.185, + libqmi l.235, libqrtr-glib l.139). Bookworm's `gobject-introspection-1.0.pc` reports + **1.74.0** — three orders of magnitude above the required 0.9.6. +- **Functional proof.** In a `debian:bookworm` container with only `libgirepository1.0-dev` + (GI 1.74) installed, a minimal `gnome.generate_gir()` meson project — mirroring MM's exact + `dependency('gobject-introspection-1.0', version: '>= 0.9.6')` assertion — configures and + builds, producing a `.gir` **and** a `.typelib` via g-ir-scanner/g-ir-compiler 1.74. + Evidence: `test-results/upstream-currency/1.2/gi-1.74-empirical.log` (PART D). +- **Preflight proof.** The adapted MM control's external build-deps resolve on bookworm + (`apt-get satisfy --dry-run`, exit 0, `libgirepository1.0-dev 1.74.0-3`); the UNADAPTED + GI-1.80 control genuinely fails (exit 100) on `gobject-introspection (>= 1.80)` and the + three missing `gir1.2-*-2.0-dev`. Evidence: `gi-1.74-empirical.log` (PARTS B/C), + `unadapted-failure.log`. + +### Stock-bookworm citation + +Every one of the four sources, **as packaged in bookworm itself**, build-depends on exactly +`gobject-introspection` + `libgirepository1.0-dev` — never the split `gir1.2-*-2.0-dev` set +(`apt-cache showsrc {modemmanager,libmbim,libqmi,libqrtr-glib}` inside bookworm, captured in +`gi-1.74-empirical.log` PART A). The substitution restores precisely bookworm's own build-dep +shape. (`libgirepository1.0-dev` Depends: `gobject-introspection`, and the virtual +`dh-sequence-gir` MM/libqrtr-glib use is Provided by `gobject-introspection 1.74.0-3` — both +confirmed in the same log.) + +## libqrtr-glib, libmbim, libqmi — only the GI adaptation + +Beyond the shared GI substitution above, these three build unmodified on bookworm. Their +`control` carries no bookworm-unsatisfiable build-dep (`debhelper-compat (= 13)` with no +`debhelper (>= x)` floor; `pkgconf`, `gi-docgen`, `gtk-doc-tools`, `help2man` all in bookworm), +their `rules` needs no pins, and their only cross-source build-deps (`libqmi` → +`libqrtr-glib-dev` + `libmbim-glib-dev`) are satisfied by the freshly built packages in the +temporary local apt repo — the bootstrap build order, not a packaging change. `debian/rules`, +maintainer scripts, symbols, and every other `debian/` file are pristine salsa copies. + +## ModemManager — three adaptations beyond the GI substitution + +MM's sid `1.24.2-2` packaging assumes a **sid/trixie** build environment in three further +ways; each adaptation is the minimal, bookworm-native fix, and each mirrors stock bookworm +`modemmanager 1.20.4-1` (whose Build-Depends — captured in `gi-1.74-empirical.log` PART A — +show `udev`, `libgirepository1.0-dev`, and no `debhelper (>= 13.11.6)` floor). + +### 1. debhelper version relax — `debian/control` + +`debhelper (>= 13.11.6)` → `debhelper (>= 13.11.4)`. Bookworm ships debhelper **13.11.4**; the +sid packaging pins `>= 13.11.6`, unsatisfiable on bookworm. Compat level is unchanged +(`debhelper-compat (= 13)`, provided by bookworm's debhelper). Nothing the build needs from +13.11.6 is used. Stock bookworm MM carries no such floor at all. + +### 2. `systemd-dev` → `udev` build-dep — `debian/control` + +`systemd-dev` is a **trixie/forky-only** package absent from bookworm. In trixie it ships both +`udev.pc` (udev base dir) and `systemd.pc` (systemd system-unit dir); on bookworm those two +`.pc` files live in separate packages (`udev.pc` in `udev`, `systemd.pc` in `systemd`). The +substitution restores the `udev.pc` half — exactly what stock bookworm MM build-deps +(`udev`). `libsystemd-dev (>= 209)` is kept unchanged (it links libsystemd for journal / suspend-resume and is unrelated to `systemd-dev`). -### 3. systemd + udev install-dir pins (**adaptation beyond the two documented ones — see below**) — `debian/rules` +### 3. systemd + udev install-dir pins — `debian/rules` -Two meson flags added to `override_dh_auto_configure`: +Two meson flags added to `override_dh_auto_configure` (companions to adaptation 2): ``` --Dsystemdsystemunitdir=/usr/lib/systemd/system --Dudevdir=/usr/lib/udev ++ -Dsystemdsystemunitdir=/usr/lib/systemd/system \ ++ -Dudevdir=/usr/lib/udev \ ``` -Both are **required companions to adaptation 2** and are why this is more than a one-token -build-dep swap: - -- **`systemdsystemunitdir`** — MM's `meson.build` calls `dependency('systemd')` whenever the - option is left empty (its default), to read `systemdsystemunitdir` from `systemd.pc`. In - trixie that `.pc` came from `systemd-dev`; after swapping to `udev` it is gone (bookworm's - `udev` ships only `udev.pc`), so `dependency('systemd')` fails with *"systemd required but - not found"*. Pinning the dir explicitly makes meson skip the `dependency('systemd')` call - entirely — exactly what **stock bookworm `modemmanager 1.20.4-1` does** (it build-deps on - `udev` and pins `-Dsystemdsystemunitdir=/lib/systemd/system` in its own `debian/rules`). -- **`udevdir`** — bookworm's `udev.pc` reports `udevdir = /lib/udev` (non-usr), but the trixie +- **`systemdsystemunitdir`** — MM's `meson.build` (l.209) calls `dependency('systemd')` when + the option is left empty (its default), to read `systemdsystemunitdir` from `systemd.pc`. + In trixie that `.pc` came from `systemd-dev`; after swapping to `udev` it is gone (bookworm's + `udev` ships only `udev.pc`), so `dependency('systemd')` fails *"systemd required but not + found"*. Pinning the dir explicitly makes meson skip the `dependency('systemd')` call — + exactly what **stock bookworm `modemmanager 1.20.4-1`** does (build-deps `udev`, pins + `-Dsystemdsystemunitdir=...` in its own `debian/rules`). +- **`udevdir`** — bookworm's `udev.pc` reports `udevdir = /lib/udev` (non-usr), but the sid `modemmanager.install` hardcodes usr-merged paths (`usr/lib/systemd`, `usr/lib/udev`). Left - unpinned, meson installs udev files to `/lib/udev` and `dh_install` aborts with *"missing - files: usr/lib/udev"*. Pinning `udevdir` (and using the usr-merged `/usr/lib/systemd/system` - for the unit dir) makes the install paths match the trixie `.install` files. - -## STOP-and-surface note: the documented adaptation set was incomplete - -The plan (A5.1) and draft documented exactly **two** adaptations: debhelper relax and -`systemd-dev → udev`. Empirically, those two alone **do not** produce a clean ModemManager -build on bookworm — the build fails first at meson (`dependency('systemd')` not found) and, -once that is worked around, again at `dh_install` (usr-merged path mismatch). Adaptation 3 -(the two `debian/rules` install-dir pins) is the minimal fix and is precisely what Debian's -own bookworm MM packaging does. It is **packaging metadata, not a source patch**: `debian/` -config only, upstream source untouched, `debian/patches/` still empty — so it does not -trip the [`POLICY.md`](../POLICY.md) no-fork gate. It is surfaced here (rather than applied -silently) because it extends the documented adaptation list; the plan/draft adaptation list -should be corrected to name these systemd/udev install-dir pins as the third bookworm -adaptation for ModemManager. + unpinned, meson installs udev files to `/lib/udev` and `dh_install` aborts *"missing files: + usr/lib/udev"*. Pinning `udevdir` (and the usr-merged `/usr/lib/systemd/system` unit dir) + makes the install paths match the `.install` files. + +## Summary of the deviation surface + +| Source | `debian/control` | `debian/rules` | +|---------------|----------------------------------------------------|---------------------------| +| ModemManager | GI-1.74 swap; debhelper `13.11.6`→`13.11.4`; `systemd-dev`→`udev` | +2 meson install-dir pins | +| libmbim | GI-1.74 swap | (pristine) | +| libqmi | GI-1.74 swap | (pristine) | +| libqrtr-glib | GI-1.74 swap | (pristine) | + +No other `debian/` file differs from the pinned salsa tree for any source (verified: +`test-results/upstream-currency/1.2/relationship-audit.txt` — zero binary-package +relationship-field changes, zero maintainer-script changes, zero package add/remove/rename; +`debdiff-.txt` ×4 — only the hunks above). diff --git a/packaging/ModemManager/debian/changelog b/packaging/ModemManager/debian/changelog index 6f95083..f357c20 100644 --- a/packaging/ModemManager/debian/changelog +++ b/packaging/ModemManager/debian/changelog @@ -1,3 +1,36 @@ +modemmanager (1.24.2-2) unstable; urgency=medium + + [ Helmut Grohne ] + * Improve cross building: (Closes: #1087277) + + Move documentation dependencies to B-D-I. + + Mark python3-dbus and python3-gi with the nocheck build profile. + + [ Arnaud Ferraris ] + * d/control: fix gobject-introspection dependencies + `libgirepository1.0-dev shouldn't be used anymore as it isn't + multiarch-friendly. Instead, use a recent `gobject-introspection` and + explicitly (build) depend on the needed `gir1.2-*-dev` packages. + (Closes: #1118899) + * d/gbp.conf: add default commit messages. + This makes it more consistent with other packages for which I'm the + primary maintainer. + * d/watch: convert to version 5. + Use the new Gitlab template for easier management, but override the + matching pattern so we only get stable (pre)releases, which have an even + minor version number. + * d/copyright: fix copyright notice for mmcli. + This is actually GPL-2+, not GPL-3. (Closes: #1116309) + + -- Arnaud Ferraris Wed, 29 Oct 2025 17:27:49 +0100 + +modemmanager (1.24.2-1) unstable; urgency=medium + + * New upstream release (Closes: #1110197) + * d/control: drop Rules-Requires-Root. + This is no longer needed. + + -- Arnaud Ferraris Mon, 11 Aug 2025 12:00:42 +0200 + modemmanager (1.24.0-1) unstable; urgency=medium * New upstream release. Final release of RC already in Trixie. diff --git a/packaging/ModemManager/debian/control b/packaging/ModemManager/debian/control index e148a5b..67d307b 100644 --- a/packaging/ModemManager/debian/control +++ b/packaging/ModemManager/debian/control @@ -11,8 +11,9 @@ Build-Depends: debhelper-compat (= 13), dh-sequence-gir, bash-completion, gettext, - libdbus-1-dev, + gobject-introspection, libgirepository1.0-dev, + libdbus-1-dev, libglib2.0-dev, libgudev-1.0-dev, libmbim-glib-dev (>= 1.32~), @@ -21,18 +22,17 @@ Build-Depends: debhelper-compat (= 13), libsystemd-dev (>= 209), meson, polkitd, - python3-dbus, - python3-gi, udev, valac (>= 0.22), xsltproc, -# Needed for building documentation - gtk-doc-tools , - libglib2.0-doc , # Needed for automated tests dbus , + python3-dbus , + python3-gi , +# Needed for building documentation +Build-Depends-Indep: gtk-doc-tools , + libglib2.0-doc , Standards-Version: 4.7.2 -Rules-Requires-Root: no Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/modemmanager.git Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/modemmanager Homepage: https://www.freedesktop.org/wiki/Software/ModemManager/ @@ -108,13 +108,14 @@ Package: libmm-glib-dev Architecture: linux-any Multi-Arch: same Section: libdevel -Depends: libmm-glib0 (= ${binary:Version}), +Depends: ${gir:Depends}, + ${misc:Depends}, + libmm-glib0 (= ${binary:Version}), gir1.2-modemmanager-1.0 (= ${binary:Version}), libglib2.0-dev, modemmanager-dev, - ${shlibs:Depends}, - ${misc:Depends}, Suggests: libmm-glib-doc +Provides: ${gir:Provides} Description: D-Bus service for managing modems - library development files ModemManager is a DBus-activated daemon which controls mobile broadband (2G/3G/4G/5G) devices and connections. Whether built-in devices, USB dongles, @@ -147,7 +148,6 @@ Section: introspection Architecture: linux-any Multi-Arch: same Depends: ${gir:Depends}, - ${shlibs:Depends}, ${misc:Depends} Description: GObject introspection data for modemmanager ModemManager is a DBus-activated daemon which controls mobile broadband diff --git a/packaging/ModemManager/debian/copyright b/packaging/ModemManager/debian/copyright index 50131e5..e4f256a 100644 --- a/packaging/ModemManager/debian/copyright +++ b/packaging/ModemManager/debian/copyright @@ -10,11 +10,6 @@ Copyright: 2011 - 2013 Aleksander Morgado 2011 - 2012 Google, Inc. License: GPL-2.0+ -Files: cli/* -Copyright: 2011 - 2012 Aleksander Morgado - 2011, 2012 Google, Inc. -License: GPL-3.0+ - Files: libqcdm/* Copyright: 2010, 2011 Red Hat, Inc. License: GPL-2.0 @@ -59,23 +54,6 @@ License: GPL-2.0 On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". -License: GPL-3.0+ - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General Public - License version 3 can be found in "/usr/share/common-licenses/GPL-3". - License: LGPL-2.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/packaging/ModemManager/debian/gbp.conf b/packaging/ModemManager/debian/gbp.conf index 2b1fbcd..ffb4461 100644 --- a/packaging/ModemManager/debian/gbp.conf +++ b/packaging/ModemManager/debian/gbp.conf @@ -9,3 +9,7 @@ postimport = dch -v%(version)s New upstream release; git add debian/changelog; d [dch] multimaint-merge = True +commit-msg = d/changelog: release version %(version)s + +[tag] +sign-tags = True diff --git a/packaging/ModemManager/debian/upstream/signing-key.asc b/packaging/ModemManager/debian/upstream/signing-key.asc index 1310c2b..d70a260 100644 --- a/packaging/ModemManager/debian/upstream/signing-key.asc +++ b/packaging/ModemManager/debian/upstream/signing-key.asc @@ -90,3 +90,138 @@ g3cO7H5+wW3dks6wOEBmeEuAi38wsG412e3LnkWLRFjyntenXZVDnMXuyDCG7Pn0 MGUY =+Ikk -----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=jlqw +-----END PGP PUBLIC KEY BLOCK----- diff --git a/packaging/ModemManager/debian/watch b/packaging/ModemManager/debian/watch index 86a2bc4..62fe308 100644 --- a/packaging/ModemManager/debian/watch +++ b/packaging/ModemManager/debian/watch @@ -1,4 +1,5 @@ -version=4 -opts="mode=git,pgpmode=gittag,uversionmangle=s/-(alpha|beta|rc)/~$1/" \ - https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@.git \ - refs/tags/(\d+\.\d?[02468][\.\-].*) +Version: 5 + +Template: Gitlab +Dist: https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@ +Matching-Pattern: .*(\d+\.\d[02468](\.\d*)?(\-(alpha|beta|rc))?\d*) diff --git a/packaging/README.md b/packaging/README.md index bf91726..85a33b6 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -29,8 +29,12 @@ re-verified end-to-end by [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins Each source's `debian/` dir is checked in at `/debian/` (`ModemManager`, `libmbim`, `libqmi`, `libqrtr-glib`), copied byte-for-byte from its pinned salsa commit ([`upstream-pins.yaml`](upstream-pins.yaml) `salsa_commit_sha`) with **zero source patches**. -Only ModemManager carries bookworm adaptations (debhelper relax, `systemd-dev → udev`, and -systemd/udev install-dir pins) — all documented, with rationale, in +All four sources carry one shared bookworm adaptation — the GObject-introspection build-deps +are swapped from the sid GI-1.80 set (`gir1.2-*-2.0-dev` + `gobject-introspection (>= 1.80)`) +to bookworm's GI-1.74 equivalent (`gobject-introspection` + `libgirepository1.0-dev`), mirroring +stock bookworm's own packaging of each source. ModemManager additionally carries three +adaptations (debhelper relax, `systemd-dev → udev`, and systemd/udev install-dir pins) — all +documented, with rationale, stock-bookworm citation, and diff shape, in [`BOOKWORM-ADAPTATIONS.md`](BOOKWORM-ADAPTATIONS.md). [`ci/build-bookworm.sh`](ci/build-bookworm.sh) `` rebuilds all four in a diff --git a/packaging/ci/expected-packages.txt b/packaging/ci/expected-packages.txt new file mode 100644 index 0000000..abec20d --- /dev/null +++ b/packaging/ci/expected-packages.txt @@ -0,0 +1,97 @@ +# expected-packages.txt — the frozen two-set package model for the ModemManager stack. +# +# WHAT THIS IS +# The exact binary packages each source produces, frozen from the NEW pinned salsa +# debian/control files (upstream-pins.yaml: MM 1.24.2-2, libmbim 1.34.0-1, libqmi +# 1.38.0-1, libqrtr-glib 1.4.0-1), for the bookworm rebuild. Two sets per source: +# +# [declared] — binary package stanzas from debian/control whose Architecture is +# NOT `all`. The build runs `dpkg-buildpackage -B` (arch-only), which +# NEVER emits Architecture: all packages, so the arch-all -doc stanzas +# (modemmanager-doc, libmm-glib-doc, libmbim-glib-doc, libqmi-glib-doc, +# libqrtr-glib-doc) are excluded here. This set is REAL (control-derived) +# and is the reference the pin-derived contract checks assert against. +# +# [all-artifact] — declared + the auto-generated `-dbgsym` package for every declared +# package that ships stripped ELF content. dbgsym packages have NO stanza +# of their own in debian/control (dh_strip/dpkg-dev synthesize them at +# build time), so they are enumerated EXPLICITLY below. This is the +# reference for 1.4's `.changes` equality (both arches) and 2.1's manifest. +# +# PROVISIONAL NOTICE (all-artifact only) +# The [all-artifact] sets below are PROVISIONAL — a best-effort enumeration derived from +# which declared packages ship ELF (shared libs, the daemon, CLI utils, proxies) vs which +# ship no ELF (-dev = headers/.so-symlink/pkgconfig/vapi/gir; gir1.2-* = .typelib, not a +# stripped ELF object). They are FINALIZED in todo 1.4 against the first clean amd64 +# `.changes`, cross-checked to equal declared+dbgsym. Any other delta at that point is a +# HARD STOP. The [declared] sets are NOT provisional — they are frozen from debian/control. +# +# DERIVATION (which declared packages get a -dbgsym) +# ELF-shipping (=> gets -dbgsym): the 9 runtime packages — modemmanager (daemon+plugins), +# libmm-glib0/libmbim-glib4/libqmi-glib5/libqrtr-glib0 (shared libs), libmbim-utils/ +# libqmi-utils (CLI binaries), libmbim-proxy/libqmi-proxy (proxy binaries). +# No ELF (=> NO -dbgsym): every *-dev (headers + unversioned .so symlink + pkgconfig + +# vapi + gir XML) and every gir1.2-*-1.0 (binary .typelib, not a stripped ELF). +# +# FORMAT — flat, one package per line under a `[ ]` header; awk/grep-friendly. + +[modemmanager declared] +modemmanager +modemmanager-dev +libmm-glib0 +libmm-glib-dev +gir1.2-modemmanager-1.0 + +[modemmanager all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +modemmanager +modemmanager-dev +libmm-glib0 +libmm-glib-dev +gir1.2-modemmanager-1.0 +modemmanager-dbgsym +libmm-glib0-dbgsym + +[libmbim declared] +libmbim-glib4 +libmbim-glib-dev +libmbim-utils +libmbim-proxy +gir1.2-mbim-1.0 + +[libmbim all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +libmbim-glib4 +libmbim-glib-dev +libmbim-utils +libmbim-proxy +gir1.2-mbim-1.0 +libmbim-glib4-dbgsym +libmbim-utils-dbgsym +libmbim-proxy-dbgsym + +[libqmi declared] +libqmi-glib-dev +libqmi-glib5 +libqmi-utils +libqmi-proxy +gir1.2-qmi-1.0 + +[libqmi all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +libqmi-glib-dev +libqmi-glib5 +libqmi-utils +libqmi-proxy +gir1.2-qmi-1.0 +libqmi-glib5-dbgsym +libqmi-utils-dbgsym +libqmi-proxy-dbgsym + +[libqrtr-glib declared] +libqrtr-glib0 +libqrtr-glib-dev +gir1.2-qrtr-1.0 + +[libqrtr-glib all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +libqrtr-glib0 +libqrtr-glib-dev +gir1.2-qrtr-1.0 +libqrtr-glib0-dbgsym diff --git a/packaging/libmbim/debian/changelog b/packaging/libmbim/debian/changelog index a218d31..e6bf386 100644 --- a/packaging/libmbim/debian/changelog +++ b/packaging/libmbim/debian/changelog @@ -1,3 +1,33 @@ +libmbim (1.34.0-1) unstable; urgency=medium + + * New upstream release + * debian/libmbim-glib4.symbols: update for new upstream release + * d/control: drop now-unneeded Priority field + `Priority: optional` is the default since Debian policy 4.7.3, let's + drop it and bump Standards-Version accordingly. + + -- Arnaud Ferraris Mon, 19 Jan 2026 23:01:48 +0100 + +libmbim (1.32.0-2) unstable; urgency=medium + + [ Guido Günther ] + * d/gbp.conf: Switch to debian/latest. + And sync options with libqmi packaging + + [ Arnaud Ferraris ] + * d/watch: convert to version 5. + Use the new Gitlab template for easier management, but override the + matching pattern so we only get stable (pre)releases, which have an even + minor version number. + * d/control: drop libgirepository1.0-dev build dependency. + This one shouldn't be used anymore as it isn't multiarch-friendly. + Instead, use a recent `gobject-introspection` and explicitly (build) + depend on the needed `gir1.2-*-dev` packages. + While at it, drop the now-obsolete `Rules-Requires-Root` and bump + `Standards-Version` as no other change is needed. (Closes: #1118875) + + -- Arnaud Ferraris Tue, 28 Oct 2025 13:03:18 +0100 + libmbim (1.32.0-1) unstable; urgency=medium * New upstream version. This is the release for the RC currently in Trixie. diff --git a/packaging/libmbim/debian/control b/packaging/libmbim/debian/control index a42135c..83c4590 100644 --- a/packaging/libmbim/debian/control +++ b/packaging/libmbim/debian/control @@ -1,6 +1,5 @@ Source: libmbim Section: libs -Priority: optional Maintainer: DebianOnMobile Maintainers Uploaders: Arnaud Ferraris , Guido Günther , @@ -9,15 +8,14 @@ Uploaders: Arnaud Ferraris , Build-Depends: debhelper-compat (= 13), bash-completion, gobject-introspection, + libgirepository1.0-dev, gtk-doc-tools , help2man, - libgirepository1.0-dev, libglib2.0-dev (>= 2.56), meson, pkgconf, Build-Depends-Indep: libglib2.0-doc -Standards-Version: 4.7.0 -Rules-Requires-Root: no +Standards-Version: 4.7.3 Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libmbim.git Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/libmbim Homepage: https://www.freedesktop.org/wiki/Software/libmbim/ @@ -36,9 +34,10 @@ Section: libdevel Architecture: any Multi-Arch: same Depends: libmbim-glib4 (= ${binary:Version}), - ${misc:Depends}, libglib2.0-dev, - gir1.2-mbim-1.0 (= ${binary:Version}), + ${gir:Depends}, + ${misc:Depends}, +Provides: ${gir:Provides} Suggests: libmbim-glib-doc Description: Header files for adding MBIM support to applications that use glib libmbim is a glib-based library for talking to WWAN modems and devices @@ -88,9 +87,7 @@ Package: gir1.2-mbim-1.0 Section: introspection Architecture: linux-any Multi-Arch: same -Depends: ${gir:Depends}, - ${shlibs:Depends}, - ${misc:Depends} +Depends: ${gir:Depends}, ${misc:Depends} Description: GObject introspection data for libmbim-glib libmbim is a glib-based library for talking to WWAN modems and devices which speak the Mobile Interface Broadband Model (MBIM) protocol. diff --git a/packaging/libmbim/debian/gbp.conf b/packaging/libmbim/debian/gbp.conf index 124c2f7..2b1fbcd 100644 --- a/packaging/libmbim/debian/gbp.conf +++ b/packaging/libmbim/debian/gbp.conf @@ -1,5 +1,11 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = debian/latest upstream-branch = upstream/latest upstream-vcs-tag=%(version)s + +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit + +[dch] +multimaint-merge = True diff --git a/packaging/libmbim/debian/libmbim-glib4.symbols b/packaging/libmbim/debian/libmbim-glib4.symbols index db512e3..bec13ef 100644 --- a/packaging/libmbim/debian/libmbim-glib4.symbols +++ b/packaging/libmbim/debian/libmbim-glib4.symbols @@ -50,6 +50,8 @@ libmbim-glib.so.4 libmbim-glib4 #MINVER#, libmbim-proxy mbim_cid_get_printable@Base 1.10.0 mbim_cid_google_get_string@Base 1.30.0 mbim_cid_google_get_type@Base 1.30.0 + mbim_cid_intel_at_tunnel_get_string@Base 1.34.0 + mbim_cid_intel_at_tunnel_get_type@Base 1.34.0 mbim_cid_intel_firmware_update_get_string@Base 1.16.0 mbim_cid_intel_firmware_update_get_type@Base 1.16.0 mbim_cid_intel_mutual_authentication_get_string@Base 1.30.0 @@ -259,6 +261,8 @@ libmbim-glib.so.4 libmbim-glib4 #MINVER#, libmbim-proxy mbim_message_indicate_status_get_raw_information_buffer@Base 1.10.0 mbim_message_indicate_status_get_service@Base 1.10.0 mbim_message_indicate_status_get_service_id@Base 1.10.0 + mbim_message_intel_at_tunnel_at_command_response_parse@Base 1.34.0 + mbim_message_intel_at_tunnel_at_command_set_new@Base 1.34.0 mbim_message_intel_firmware_update_modem_reboot_set_new@Base 1.16.0 mbim_message_intel_firmware_update_v2_modem_reboot_set_new@Base 1.28.0 mbim_message_intel_mutual_authentication_fcc_lock_query_new@Base 1.30.0 @@ -459,6 +463,7 @@ libmbim-glib.so.4 libmbim-glib4 #MINVER#, libmbim-proxy mbim_message_signal_state_query_new@Base 1.10.0 mbim_message_signal_state_response_parse@Base 1.10.0 mbim_message_signal_state_set_new@Base 1.10.0 + mbim_message_sms_configuration_notification_parse@Base 1.34.0 mbim_message_sms_configuration_query_new@Base 1.10.0 mbim_message_sms_configuration_response_parse@Base 1.10.0 mbim_message_sms_configuration_set_new@Base 1.10.0 diff --git a/packaging/libmbim/debian/watch b/packaging/libmbim/debian/watch index 86a2bc4..62fe308 100644 --- a/packaging/libmbim/debian/watch +++ b/packaging/libmbim/debian/watch @@ -1,4 +1,5 @@ -version=4 -opts="mode=git,pgpmode=gittag,uversionmangle=s/-(alpha|beta|rc)/~$1/" \ - https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@.git \ - refs/tags/(\d+\.\d?[02468][\.\-].*) +Version: 5 + +Template: Gitlab +Dist: https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@ +Matching-Pattern: .*(\d+\.\d[02468](\.\d*)?(\-(alpha|beta|rc))?\d*) diff --git a/packaging/libqmi/debian/changelog b/packaging/libqmi/debian/changelog index 9f136bd..79e0c97 100644 --- a/packaging/libqmi/debian/changelog +++ b/packaging/libqmi/debian/changelog @@ -1,3 +1,54 @@ +libqmi (1.38.0-1) unstable; urgency=medium + + * Upload to unstable + * New upstream stable release + * Link docs to devhelp. + * Update symbols + + -- Guido Günther Thu, 08 Jan 2026 19:23:18 +0100 + +libqmi (1.37.95-1) experimental; urgency=medium + + * New upstream release + * Update upstream signing from + https://lists.freedesktop.org/archives/networkmanager/2024-December/000366.html + * Update symbols + + -- Guido Günther Wed, 26 Nov 2025 10:13:58 +0100 + +libqmi (1.37.1-1) experimental; urgency=medium + + * New upstream development snapshot + * Upload to experimental + * signing-key: Add key used for dev snapshots + * d/watch: Allow for development snapshots + * d/gbp.conf: Switch to debian/experimental + * Use gi-docgen. Upstream switched away from gtk-doc + * Add new symbols + + -- Guido Günther Tue, 03 Jun 2025 20:37:05 +0200 + +libqmi (1.36.0-2) unstable; urgency=medium + + [ Guido Günther ] + * d/gbp.conf: Switch to debian/latest. + + [ Arnaud Ferraris ] + * d/watch: convert to version 5. + Use the new Gitlab template for easier management, but override the + matching pattern so we only get stable (pre)releases, which have an even + minor version number. + * d/control: drop libgirepository1.0-dev build dependency. + This one shouldn't be used anymore as it isn't multiarch-friendly. + Instead, use a recent `gobject-introspection` and explicitly (build) + depend on the needed `gir1.2-*-dev` packages. + While at it, drop the now-obsolete `Rules-Requires-Root` and bump + `Standards-Version` as no other change is needed. (Closes: #1118881) + * d/copyright: drop reference to old FSF address. + Replace it with the usual link to http://www.gnu.org/licenses/. + + -- Arnaud Ferraris Tue, 28 Oct 2025 13:03:13 +0100 + libqmi (1.36.0-1) unstable; urgency=medium * New upstream release diff --git a/packaging/libqmi/debian/control b/packaging/libqmi/debian/control index 887f043..6ab05ed 100644 --- a/packaging/libqmi/debian/control +++ b/packaging/libqmi/debian/control @@ -8,9 +8,9 @@ Uploaders: Arnaud Ferraris , Build-Depends: debhelper-compat (= 13), bash-completion, gobject-introspection, - gtk-doc-tools , - help2man, libgirepository1.0-dev, + gi-docgen , + help2man, libglib2.0-dev, libgudev-1.0-dev, libmbim-glib-dev, @@ -18,8 +18,7 @@ Build-Depends: debhelper-compat (= 13), meson, pkgconf, Build-Depends-Indep: libglib2.0-doc -Standards-Version: 4.7.0 -Rules-Requires-Root: no +Standards-Version: 4.7.2 Section: libs Homepage: https://www.freedesktop.org/wiki/Software/libqmi Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libqmi.git @@ -30,12 +29,13 @@ Section: libdevel Architecture: linux-any Multi-Arch: same Depends: ${misc:Depends}, - gir1.2-qmi-1.0 (= ${binary:Version}), libmbim-glib-dev, libqmi-glib5 (= ${binary:Version}), libglib2.0-dev, libqrtr-glib-dev, + ${gir:Depends}, Suggests: libqmi-glib-doc, +Provides: ${gir:Provides} Description: Header files for adding QMI support to applications that use glib This package contains the header and pkg-config files for development applications using QMI functionality from applications that use glib. @@ -84,8 +84,6 @@ Package: gir1.2-qmi-1.0 Section: introspection Architecture: linux-any Multi-Arch: same -Depends: ${gir:Depends}, - ${shlibs:Depends}, - ${misc:Depends} +Depends: ${gir:Depends}, ${misc:Depends} Description: GObject introspection data for libqmi-glib This package contains introspection data for the libqmi-glib library. diff --git a/packaging/libqmi/debian/copyright b/packaging/libqmi/debian/copyright index 76fbfad..8fd8aec 100644 --- a/packaging/libqmi/debian/copyright +++ b/packaging/libqmi/debian/copyright @@ -59,9 +59,8 @@ License: GPL-2+ details. . You should have received a copy of the GNU General Public - License along with this package; if not, write to the Free - Software Foundation, Inc., 51 Franklin St, Fifth Floor, - Boston, MA 02110-1301 USA + License along with this package. If not, see + . . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file diff --git a/packaging/libqmi/debian/gbp.conf b/packaging/libqmi/debian/gbp.conf index 4476d9d..2b1fbcd 100644 --- a/packaging/libqmi/debian/gbp.conf +++ b/packaging/libqmi/debian/gbp.conf @@ -1,6 +1,6 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = debian/latest upstream-branch = upstream/latest upstream-vcs-tag=%(version)s diff --git a/packaging/libqmi/debian/libqmi-glib-doc.doc-base b/packaging/libqmi/debian/libqmi-glib-doc.doc-base index 37a150e..92ba4ee 100644 --- a/packaging/libqmi/debian/libqmi-glib-doc.doc-base +++ b/packaging/libqmi/debian/libqmi-glib-doc.doc-base @@ -5,5 +5,5 @@ Abstract: API documentation of libqmi-glib. Section: Programming/C Format: HTML -Index: /usr/share/doc/libqmi-glib-doc/html/index.html -Files: /usr/share/doc/libqmi-glib-doc/html/*.html +Index: /usr/share/doc/libqmi-glib-doc/libqmi-glib-1.0/index.html +Files: /usr/share/doc/libqmi-glib-doc/libqmi-glib-1.0/*.html diff --git a/packaging/libqmi/debian/libqmi-glib-doc.install b/packaging/libqmi/debian/libqmi-glib-doc.install index 1776e6f..2192893 100644 --- a/packaging/libqmi/debian/libqmi-glib-doc.install +++ b/packaging/libqmi/debian/libqmi-glib-doc.install @@ -1 +1 @@ -usr/share/gtk-doc/html/libqmi-glib +usr/share/doc/libqmi-glib-1.0 usr/share/doc/libqmi-glib-doc/ diff --git a/packaging/libqmi/debian/libqmi-glib-doc.links b/packaging/libqmi/debian/libqmi-glib-doc.links index f8d2dbf..a0ad7c5 100644 --- a/packaging/libqmi/debian/libqmi-glib-doc.links +++ b/packaging/libqmi/debian/libqmi-glib-doc.links @@ -1 +1 @@ -usr/share/gtk-doc/html/libqmi-glib usr/share/doc/libqmi-glib-doc/html +usr/share/doc/libqmi-glib-doc/libqmi-glib-1.0 usr/share/devhelp/books/libqmi-glib-1.0 diff --git a/packaging/libqmi/debian/libqmi-glib5.symbols b/packaging/libqmi/debian/libqmi-glib5.symbols index 881406e..1853f81 100644 --- a/packaging/libqmi/debian/libqmi-glib5.symbols +++ b/packaging/libqmi/debian/libqmi-glib5.symbols @@ -169,6 +169,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_fox_get_firmware_version@Base 1.32.0 qmi_client_fox_get_firmware_version_finish@Base 1.32.0 qmi_client_fox_get_type@Base 1.32.0 + qmi_client_fox_set_fcc_authentication@Base 1.37.1 + qmi_client_fox_set_fcc_authentication_finish@Base 1.37.1 qmi_client_gas_dms_get_ethernet_pdu_mac_address@Base 1.35.6 qmi_client_gas_dms_get_ethernet_pdu_mac_address_finish@Base 1.35.6 qmi_client_gas_dms_get_firmware_auto_sim@Base 1.35.95 @@ -200,6 +202,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_ims_get_ims_services_enabled_setting@Base 1.34.0 qmi_client_ims_get_ims_services_enabled_setting_finish@Base 1.34.0 qmi_client_ims_get_type@Base 1.34.0 + qmi_client_ims_set_ims_services_enabled_setting@Base 1.37.95 + qmi_client_ims_set_ims_services_enabled_setting_finish@Base 1.37.95 qmi_client_imsa_bind@Base 1.35.6 qmi_client_imsa_bind_finish@Base 1.35.6 qmi_client_imsa_get_ims_registration_status@Base 1.34.0 @@ -209,6 +213,11 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_imsa_get_type@Base 1.34.0 qmi_client_imsa_register_indications@Base 1.35.6 qmi_client_imsa_register_indications_finish@Base 1.35.6 + qmi_client_imsdcm_get_type@Base 1.37.95 + qmi_client_imsdcm_pdp_activate_request@Base 1.37.95 + qmi_client_imsdcm_pdp_activate_request_finish@Base 1.37.95 + qmi_client_imsdcm_pdp_deactivate_request@Base 1.37.95 + qmi_client_imsdcm_pdp_deactivate_request_finish@Base 1.37.95 qmi_client_imsp_get_enabler_state@Base 1.34.0 qmi_client_imsp_get_enabler_state_finish@Base 1.34.0 qmi_client_imsp_get_type@Base 1.34.0 @@ -223,11 +232,17 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_loc_get_operation_mode_finish@Base 1.22.0 qmi_client_loc_get_predicted_orbits_data_source@Base 1.22.0 qmi_client_loc_get_predicted_orbits_data_source_finish@Base 1.22.0 + qmi_client_loc_get_predicted_orbits_data_validity@Base 1.38.0 + qmi_client_loc_get_predicted_orbits_data_validity_finish@Base 1.38.0 qmi_client_loc_get_server@Base 1.22.0 qmi_client_loc_get_server_finish@Base 1.22.0 qmi_client_loc_get_type@Base 1.20.0 + qmi_client_loc_inject_position@Base 1.37.95 + qmi_client_loc_inject_position_finish@Base 1.37.95 qmi_client_loc_inject_predicted_orbits_data@Base 1.22.0 qmi_client_loc_inject_predicted_orbits_data_finish@Base 1.22.0 + qmi_client_loc_inject_utc_time@Base 1.37.95 + qmi_client_loc_inject_utc_time_finish@Base 1.37.95 qmi_client_loc_inject_xtra_data@Base 1.22.0 qmi_client_loc_inject_xtra_data_finish@Base 1.22.0 qmi_client_loc_register_events@Base 1.22.0 @@ -258,6 +273,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_nas_get_cell_location_info_finish@Base 1.10.0 qmi_client_nas_get_drx@Base 1.28.6 qmi_client_nas_get_drx_finish@Base 1.28.6 + qmi_client_nas_get_endc_config@Base 1.37.95 + qmi_client_nas_get_endc_config_finish@Base 1.37.95 qmi_client_nas_get_home_network@Base 1.6.0 qmi_client_nas_get_home_network_finish@Base 1.6.0 qmi_client_nas_get_lte_cphy_ca_info@Base 1.16.0 @@ -289,6 +306,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_nas_get_tx_rx_info@Base 1.6.0 qmi_client_nas_get_tx_rx_info_finish@Base 1.6.0 qmi_client_nas_get_type@Base 1.6.0 + qmi_client_nas_incremental_network_scan@Base 1.37.95 + qmi_client_nas_incremental_network_scan_finish@Base 1.37.95 qmi_client_nas_initiate_network_register@Base 1.6.0 qmi_client_nas_initiate_network_register_finish@Base 1.6.0 qmi_client_nas_network_scan@Base 1.6.0 @@ -328,6 +347,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_pbm_get_all_capabilities_finish@Base 1.6.0 qmi_client_pbm_get_capabilities@Base 1.6.0 qmi_client_pbm_get_capabilities_finish@Base 1.6.0 + qmi_client_pbm_get_emergency_list@Base 1.37.95 + qmi_client_pbm_get_emergency_list_finish@Base 1.37.95 qmi_client_pbm_get_type@Base 1.6.0 qmi_client_pbm_indication_register@Base 1.6.0 qmi_client_pbm_indication_register_finish@Base 1.6.0 @@ -380,6 +401,12 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_pds_set_gps_service_state@Base 1.6.0 qmi_client_pds_set_gps_service_state_finish@Base 1.6.0 qmi_client_peek_device@Base 1.6.0 + qmi_client_qos_bind_mux_data_port@Base 1.37.95 + qmi_client_qos_bind_mux_data_port_finish@Base 1.37.95 + qmi_client_qos_bind_subscription@Base 1.37.95 + qmi_client_qos_bind_subscription_finish@Base 1.37.95 + qmi_client_qos_get_bind_subscription@Base 1.37.95 + qmi_client_qos_get_bind_subscription_finish@Base 1.37.95 qmi_client_qos_get_flow_status@Base 1.22.0 qmi_client_qos_get_flow_status_finish@Base 1.22.0 qmi_client_qos_get_network_status@Base 1.22.0 @@ -448,6 +475,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_uim_unblock_pin_finish@Base 1.14.0 qmi_client_uim_verify_pin@Base 1.14.0 qmi_client_uim_verify_pin_finish@Base 1.14.0 + qmi_client_uim_write_record@Base 1.37.95 + qmi_client_uim_write_record_finish@Base 1.37.95 qmi_client_voice_answer_call@Base 1.14.0 qmi_client_voice_answer_call_finish@Base 1.14.0 qmi_client_voice_answer_ussd@Base 1.25.900 @@ -496,6 +525,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_wds_bind_data_port_finish@Base 1.28.6 qmi_client_wds_bind_mux_data_port@Base 1.18.0 qmi_client_wds_bind_mux_data_port_finish@Base 1.18.0 + qmi_client_wds_bind_subscription@Base 1.37.95 + qmi_client_wds_bind_subscription_finish@Base 1.37.95 qmi_client_wds_configure_profile_event_list@Base 1.34.0 qmi_client_wds_configure_profile_event_list_finish@Base 1.34.0 qmi_client_wds_create_profile@Base 1.18.0 @@ -504,6 +535,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_wds_delete_profile_finish@Base 1.18.0 qmi_client_wds_get_autoconnect_settings@Base 1.14.0 qmi_client_wds_get_autoconnect_settings_finish@Base 1.14.0 + qmi_client_wds_get_bind_subscription@Base 1.37.95 + qmi_client_wds_get_bind_subscription_finish@Base 1.37.95 qmi_client_wds_get_channel_rates@Base 1.20.0 qmi_client_wds_get_channel_rates_finish@Base 1.20.0 qmi_client_wds_get_current_data_bearer_technology@Base 1.6.0 @@ -569,13 +602,19 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_wds_swi_create_profile_indexed_finish@Base 1.22.0 qmi_client_wms_delete@Base 1.6.0 qmi_client_wms_delete_finish@Base 1.6.0 + qmi_client_wms_get_broadcast_config@Base 1.37.1 + qmi_client_wms_get_broadcast_config_finish@Base 1.37.1 qmi_client_wms_get_message_protocol@Base 1.6.0 qmi_client_wms_get_message_protocol_finish@Base 1.6.0 qmi_client_wms_get_routes@Base 1.6.0 qmi_client_wms_get_routes_finish@Base 1.6.0 qmi_client_wms_get_supported_messages@Base 1.14.0 qmi_client_wms_get_supported_messages_finish@Base 1.14.0 + qmi_client_wms_get_transport_network_registration_status@Base 1.37.95 + qmi_client_wms_get_transport_network_registration_status_finish@Base 1.37.95 qmi_client_wms_get_type@Base 1.6.0 + qmi_client_wms_indication_register@Base 1.37.95 + qmi_client_wms_indication_register_finish@Base 1.37.95 qmi_client_wms_list_messages@Base 1.6.0 qmi_client_wms_list_messages_finish@Base 1.6.0 qmi_client_wms_modify_tag@Base 1.6.0 @@ -592,6 +631,10 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_client_wms_send_ack_finish@Base 1.28.6 qmi_client_wms_send_from_memory_storage@Base 1.6.0 qmi_client_wms_send_from_memory_storage_finish@Base 1.6.0 + qmi_client_wms_set_broadcast_activation@Base 1.37.1 + qmi_client_wms_set_broadcast_activation_finish@Base 1.37.1 + qmi_client_wms_set_broadcast_config@Base 1.37.1 + qmi_client_wms_set_broadcast_config_finish@Base 1.37.1 qmi_client_wms_set_event_report@Base 1.6.0 qmi_client_wms_set_event_report_finish@Base 1.6.0 qmi_client_wms_set_routes@Base 1.6.0 @@ -613,6 +656,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_device_add_link_flags_build_string_from_mask@Base 1.30.2 qmi_device_add_link_flags_get_type@Base 1.30.2 qmi_device_add_link_with_flags@Base 1.30.2 + qmi_device_add_link_with_flags_and_initial_mux_id@Base 1.37.95 + qmi_device_add_link_with_flags_and_initial_mux_id_finish@Base 1.37.95 qmi_device_add_link_with_flags_finish@Base 1.30.2 qmi_device_allocate_client@Base 1.6.0 qmi_device_allocate_client_finish@Base 1.6.0 @@ -755,6 +800,16 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_gas_firmware_listing_mode_get_type@Base 1.24.6 qmi_gas_usb_composition_endpoint_type_get_string@Base 1.32.0 qmi_gas_usb_composition_endpoint_type_get_type@Base 1.32.0 + qmi_ims_call_mode_preference_get_string@Base 1.37.95 + qmi_ims_call_mode_preference_get_type@Base 1.37.95 + qmi_ims_dcm_apn_type_get_string@Base 1.37.95 + qmi_ims_dcm_apn_type_get_type@Base 1.37.95 + qmi_ims_dcm_instance_id_get_string@Base 1.37.95 + qmi_ims_dcm_instance_id_get_type@Base 1.37.95 + qmi_ims_dcm_ip_familiy_get_string@Base 1.37.95 + qmi_ims_dcm_ip_familiy_get_type@Base 1.37.95 + qmi_ims_dcm_rat_type_get_string@Base 1.37.95 + qmi_ims_dcm_rat_type_get_type@Base 1.37.95 qmi_imsa_ims_registration_status_get_string@Base 1.34.0 qmi_imsa_ims_registration_status_get_type@Base 1.34.0 qmi_imsa_registration_technology_get_string@Base 1.34.0 @@ -787,6 +842,22 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_dsd_system_status_output_get_type@Base 1.32.0 qmi_indication_dsd_system_status_output_ref@Base 1.32.0 qmi_indication_dsd_system_status_output_unref@Base 1.32.0 + qmi_indication_ims_ims_services_enabled_setting_indication_parse@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_autoconfig_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_carrier_config@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_presence_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_rcs_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_service_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_sms_service_enable@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_ussd_service_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_ut_service_enable@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_video_telephony_service_enable@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_voice_over_lte_enable@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_voice_wifi_service_enable@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_ims_xdm_client_enabled@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_get_type@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_ref@Base 1.37.95 + qmi_indication_ims_ims_services_enabled_setting_output_unref@Base 1.37.95 qmi_indication_imsa_ims_registration_status_changed_indication_parse@Base 1.35.6 qmi_indication_imsa_ims_registration_status_changed_output_get_ims_registration_error_code@Base 1.35.6 qmi_indication_imsa_ims_registration_status_changed_output_get_ims_registration_error_message@Base 1.35.6 @@ -809,6 +880,15 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_imsa_ims_services_status_changed_output_get_type@Base 1.35.6 qmi_indication_imsa_ims_services_status_changed_output_ref@Base 1.35.6 qmi_indication_imsa_ims_services_status_changed_output_unref@Base 1.35.6 + qmi_indication_imsdcm_pdp_activate_indication_parse@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_address_info@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_instance_id@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_pdp_id@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_pdp_request_sequence_number@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_result@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_get_type@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_ref@Base 1.37.95 + qmi_indication_imsdcm_pdp_activate_output_unref@Base 1.37.95 qmi_indication_loc_delete_assistance_data_indication_parse@Base 1.34.0 qmi_indication_loc_delete_assistance_data_output_get_indication_status@Base 1.22.0 qmi_indication_loc_delete_assistance_data_output_get_type@Base 1.22.0 @@ -849,6 +929,12 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_loc_get_predicted_orbits_data_source_output_get_type@Base 1.22.0 qmi_indication_loc_get_predicted_orbits_data_source_output_ref@Base 1.22.0 qmi_indication_loc_get_predicted_orbits_data_source_output_unref@Base 1.22.0 + qmi_indication_loc_get_predicted_orbits_data_validity_indication_parse@Base 1.38.0 + qmi_indication_loc_get_predicted_orbits_data_validity_output_get_indication_status@Base 1.38.0 + qmi_indication_loc_get_predicted_orbits_data_validity_output_get_type@Base 1.38.0 + qmi_indication_loc_get_predicted_orbits_data_validity_output_get_validity_info@Base 1.38.0 + qmi_indication_loc_get_predicted_orbits_data_validity_output_ref@Base 1.38.0 + qmi_indication_loc_get_predicted_orbits_data_validity_output_unref@Base 1.38.0 qmi_indication_loc_get_server_indication_parse@Base 1.34.0 qmi_indication_loc_get_server_output_get_indication_status@Base 1.22.0 qmi_indication_loc_get_server_output_get_ipv4@Base 1.22.0 @@ -866,12 +952,45 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_loc_gnss_sv_info_output_list_element_get_type@Base 1.32.0 qmi_indication_loc_gnss_sv_info_output_ref@Base 1.22.0 qmi_indication_loc_gnss_sv_info_output_unref@Base 1.22.0 + qmi_indication_loc_inject_position_indication_parse@Base 1.37.95 + qmi_indication_loc_inject_position_output_get_indication_status@Base 1.37.95 + qmi_indication_loc_inject_position_output_get_type@Base 1.37.95 + qmi_indication_loc_inject_position_output_ref@Base 1.37.95 + qmi_indication_loc_inject_position_output_unref@Base 1.37.95 + qmi_indication_loc_inject_position_request_indication_parse@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_get_horizontal_uncertainty_circular@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_get_latitude@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_get_longitude@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_get_type@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_get_utc_timestamp@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_ref@Base 1.37.95 + qmi_indication_loc_inject_position_request_output_unref@Base 1.37.95 qmi_indication_loc_inject_predicted_orbits_data_indication_parse@Base 1.34.0 qmi_indication_loc_inject_predicted_orbits_data_output_get_indication_status@Base 1.22.0 qmi_indication_loc_inject_predicted_orbits_data_output_get_part_number@Base 1.22.0 qmi_indication_loc_inject_predicted_orbits_data_output_get_type@Base 1.22.0 qmi_indication_loc_inject_predicted_orbits_data_output_ref@Base 1.22.0 qmi_indication_loc_inject_predicted_orbits_data_output_unref@Base 1.22.0 + qmi_indication_loc_inject_predicted_orbits_request_indication_parse@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_allowed_sizes@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_file_info@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_server_list@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_server_update_mask@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_type@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_update_period_seconds@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_get_update_type@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_ref@Base 1.37.95 + qmi_indication_loc_inject_predicted_orbits_request_output_unref@Base 1.37.95 + qmi_indication_loc_inject_time_request_indication_parse@Base 1.37.95 + qmi_indication_loc_inject_time_request_output_get_time_server_info@Base 1.37.95 + qmi_indication_loc_inject_time_request_output_get_type@Base 1.37.95 + qmi_indication_loc_inject_time_request_output_ref@Base 1.37.95 + qmi_indication_loc_inject_time_request_output_unref@Base 1.37.95 + qmi_indication_loc_inject_utc_time_indication_parse@Base 1.37.95 + qmi_indication_loc_inject_utc_time_output_get_indication_status@Base 1.37.95 + qmi_indication_loc_inject_utc_time_output_get_type@Base 1.37.95 + qmi_indication_loc_inject_utc_time_output_ref@Base 1.37.95 + qmi_indication_loc_inject_utc_time_output_unref@Base 1.37.95 qmi_indication_loc_inject_xtra_data_indication_parse@Base 1.34.0 qmi_indication_loc_inject_xtra_data_output_get_indication_status@Base 1.22.0 qmi_indication_loc_inject_xtra_data_output_get_part_number@Base 1.22.0 @@ -958,6 +1077,14 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_nas_event_report_output_ref@Base 1.6.0 qmi_indication_nas_event_report_output_rf_band_information_element_get_type@Base 1.32.0 qmi_indication_nas_event_report_output_unref@Base 1.6.0 + qmi_indication_nas_incremental_network_scan_indication_parse@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_get_network_information@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_get_network_information_gir@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_get_status@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_get_type@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_network_information_element_get_type@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_ref@Base 1.37.95 + qmi_indication_nas_incremental_network_scan_output_unref@Base 1.37.95 qmi_indication_nas_network_reject_indication_parse@Base 1.34.0 qmi_indication_nas_network_reject_output_get_closed_subscriber_group@Base 1.30.2 qmi_indication_nas_network_reject_output_get_plmn@Base 1.30.2 @@ -1307,6 +1434,17 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_indication_wms_smsc_address_output_get_type@Base 1.14.0 qmi_indication_wms_smsc_address_output_ref@Base 1.14.0 qmi_indication_wms_smsc_address_output_unref@Base 1.14.0 + qmi_indication_wms_transport_network_registration_status_indication_parse@Base 1.37.95 + qmi_indication_wms_transport_network_registration_status_output_get_registration_status@Base 1.37.95 + qmi_indication_wms_transport_network_registration_status_output_get_type@Base 1.37.95 + qmi_indication_wms_transport_network_registration_status_output_ref@Base 1.37.95 + qmi_indication_wms_transport_network_registration_status_output_unref@Base 1.37.95 + qmi_loc_altitude_dependency_get_string@Base 1.37.95 + qmi_loc_altitude_dependency_get_type@Base 1.37.95 + qmi_loc_altitude_source_get_string@Base 1.37.95 + qmi_loc_altitude_source_get_type@Base 1.37.95 + qmi_loc_altitude_uncertainty_get_string@Base 1.37.95 + qmi_loc_altitude_uncertainty_get_type@Base 1.37.95 qmi_loc_delete_cell_database_build_string_from_mask@Base 1.22.0 qmi_loc_delete_cell_database_get_type@Base 1.22.0 qmi_loc_delete_clock_info_build_string_from_mask@Base 1.22.0 @@ -1323,6 +1461,14 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_loc_health_status_get_type@Base 1.22.0 qmi_loc_indication_status_get_string@Base 1.22.0 qmi_loc_indication_status_get_type@Base 1.22.0 + qmi_loc_injected_orbit_file_type_get_string@Base 1.37.95 + qmi_loc_injected_orbit_file_type_get_type@Base 1.37.95 + qmi_loc_injected_orbit_server_param_update_build_string_from_mask@Base 1.37.95 + qmi_loc_injected_orbit_server_param_update_get_type@Base 1.37.95 + qmi_loc_injected_orbit_update_type_get_string@Base 1.37.95 + qmi_loc_injected_orbit_update_type_get_type@Base 1.37.95 + qmi_loc_injected_time_source_get_string@Base 1.37.95 + qmi_loc_injected_time_source_get_type@Base 1.37.95 qmi_loc_intermediate_report_state_get_string@Base 1.20.0 qmi_loc_intermediate_report_state_get_type@Base 1.20.0 qmi_loc_lock_type_get_string@Base 1.28.6 @@ -1333,6 +1479,10 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_loc_nmea_type_get_type@Base 1.25.900 qmi_loc_operation_mode_get_string@Base 1.22.0 qmi_loc_operation_mode_get_type@Base 1.22.0 + qmi_loc_position_source_get_string@Base 1.37.95 + qmi_loc_position_source_get_type@Base 1.37.95 + qmi_loc_position_source_provider_get_string@Base 1.37.95 + qmi_loc_position_source_provider_get_type@Base 1.37.95 qmi_loc_predicted_orbits_data_format_get_string@Base 1.22.0 qmi_loc_predicted_orbits_data_format_get_type@Base 1.22.0 qmi_loc_reliability_get_string@Base 1.22.0 @@ -2175,6 +2325,19 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_fox_get_firmware_version_output_ref@Base 1.32.0 qmi_message_fox_get_firmware_version_output_unref@Base 1.32.0 qmi_message_fox_get_firmware_version_response_parse@Base 1.34.0 + qmi_message_fox_set_fcc_authentication_input_get_magic_number@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_get_magic_string@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_get_type@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_new@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_ref@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_set_magic_number@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_set_magic_string@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_input_unref@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_output_get_result@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_output_get_type@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_output_ref@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_output_unref@Base 1.37.1 + qmi_message_fox_set_fcc_authentication_response_parse@Base 1.37.1 qmi_message_gas_dms_get_ethernet_pdu_mac_address_output_get_mac_address_0@Base 1.35.6 qmi_message_gas_dms_get_ethernet_pdu_mac_address_output_get_mac_address_1@Base 1.35.6 qmi_message_gas_dms_get_ethernet_pdu_mac_address_output_get_result@Base 1.35.6 @@ -2325,6 +2488,41 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_ims_get_ims_services_enabled_setting_output_ref@Base 1.34.0 qmi_message_ims_get_ims_services_enabled_setting_output_unref@Base 1.34.0 qmi_message_ims_get_ims_services_enabled_setting_response_parse@Base 1.34.0 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_autoconfig_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_call_mode_preference@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_carrier_config@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_presence_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_rcs_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_service_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_sms_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_ussd_service_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_ut_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_video_telephony_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_voice_over_lte_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_voice_wifi_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_ims_xdm_client_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_get_type@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_new@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_ref@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_autoconfig_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_call_mode_preference@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_carrier_config@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_presence_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_rcs_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_service_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_sms_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_ussd_service_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_ut_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_video_telephony_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_voice_over_lte_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_voice_wifi_service_enable@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_set_ims_xdm_client_enabled@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_input_unref@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_output_get_result@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_output_get_type@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_output_ref@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_output_unref@Base 1.37.95 + qmi_message_ims_set_ims_services_enabled_setting_response_parse@Base 1.37.95 qmi_message_imsa_bind_input_get_binding@Base 1.35.6 qmi_message_imsa_bind_input_get_type@Base 1.35.6 qmi_message_imsa_bind_input_new@Base 1.35.6 @@ -2373,6 +2571,43 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_imsa_register_indications_output_ref@Base 1.35.6 qmi_message_imsa_register_indications_output_unref@Base 1.35.6 qmi_message_imsa_register_indications_response_parse@Base 1.35.6 + qmi_message_imsdcm_pdp_activate_request_input_get_connection_parameters@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_get_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_get_pdp_request_sequence_number@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_get_pdp_slot_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_get_pdp_subscription_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_get_type@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_new@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_ref@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_set_connection_parameters@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_set_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_set_pdp_request_sequence_number@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_set_pdp_slot_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_set_pdp_subscription_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_input_unref@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_get_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_get_pdp_id@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_get_pdp_request_sequence_number@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_get_result@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_get_type@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_ref@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_output_unref@Base 1.37.95 + qmi_message_imsdcm_pdp_activate_request_response_parse@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_get_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_get_pdp_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_get_type@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_new@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_ref@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_set_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_set_pdp_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_input_unref@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_get_instance_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_get_pdp_id@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_get_result@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_get_type@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_ref@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_output_unref@Base 1.37.95 + qmi_message_imsdcm_pdp_deactivate_request_response_parse@Base 1.37.95 qmi_message_imsp_get_enabler_state_output_get_enabler_state@Base 1.34.0 qmi_message_imsp_get_enabler_state_output_get_result@Base 1.34.0 qmi_message_imsp_get_enabler_state_output_get_type@Base 1.34.0 @@ -2424,6 +2659,11 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_loc_get_predicted_orbits_data_source_output_ref@Base 1.22.0 qmi_message_loc_get_predicted_orbits_data_source_output_unref@Base 1.22.0 qmi_message_loc_get_predicted_orbits_data_source_response_parse@Base 1.34.0 + qmi_message_loc_get_predicted_orbits_data_validity_output_get_result@Base 1.38.0 + qmi_message_loc_get_predicted_orbits_data_validity_output_get_type@Base 1.38.0 + qmi_message_loc_get_predicted_orbits_data_validity_output_ref@Base 1.38.0 + qmi_message_loc_get_predicted_orbits_data_validity_output_unref@Base 1.38.0 + qmi_message_loc_get_predicted_orbits_data_validity_response_parse@Base 1.38.0 qmi_message_loc_get_server_input_get_server_address_type@Base 1.22.0 qmi_message_loc_get_server_input_get_server_type@Base 1.22.0 qmi_message_loc_get_server_input_get_type@Base 1.22.0 @@ -2437,6 +2677,63 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_loc_get_server_output_ref@Base 1.22.0 qmi_message_loc_get_server_output_unref@Base 1.22.0 qmi_message_loc_get_server_response_parse@Base 1.34.0 + qmi_message_loc_inject_position_input_get_altitude_from_ellipsoid@Base 1.37.95 + qmi_message_loc_inject_position_input_get_altitude_from_sealevel@Base 1.37.95 + qmi_message_loc_inject_position_input_get_altitude_source@Base 1.37.95 + qmi_message_loc_inject_position_input_get_gps_date_time@Base 1.37.95 + qmi_message_loc_inject_position_input_get_horizontal_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_get_horizontal_reliability@Base 1.37.95 + qmi_message_loc_inject_position_input_get_horizontal_uncertainty_circular@Base 1.37.95 + qmi_message_loc_inject_position_input_get_latitude@Base 1.37.95 + qmi_message_loc_inject_position_input_get_longitude@Base 1.37.95 + qmi_message_loc_inject_position_input_get_number_satellites_in_fix@Base 1.37.95 + qmi_message_loc_inject_position_input_get_position_source@Base 1.37.95 + qmi_message_loc_inject_position_input_get_position_source_provider@Base 1.37.95 + qmi_message_loc_inject_position_input_get_raw_horizontal_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_get_raw_horizontal_uncertainty_circular@Base 1.37.95 + qmi_message_loc_inject_position_input_get_requested_position_injection@Base 1.37.95 + qmi_message_loc_inject_position_input_get_satellites_used@Base 1.37.95 + qmi_message_loc_inject_position_input_get_speed_east_north_up@Base 1.37.95 + qmi_message_loc_inject_position_input_get_speed_uncertainty_east_north_up@Base 1.37.95 + qmi_message_loc_inject_position_input_get_time_uncertainty@Base 1.37.95 + qmi_message_loc_inject_position_input_get_timestamp_age@Base 1.37.95 + qmi_message_loc_inject_position_input_get_type@Base 1.37.95 + qmi_message_loc_inject_position_input_get_utc_timestamp@Base 1.37.95 + qmi_message_loc_inject_position_input_get_vertical_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_get_vertical_reliability@Base 1.37.95 + qmi_message_loc_inject_position_input_get_vertical_uncertainty@Base 1.37.95 + qmi_message_loc_inject_position_input_new@Base 1.37.95 + qmi_message_loc_inject_position_input_ref@Base 1.37.95 + qmi_message_loc_inject_position_input_set_altitude_from_ellipsoid@Base 1.37.95 + qmi_message_loc_inject_position_input_set_altitude_from_sealevel@Base 1.37.95 + qmi_message_loc_inject_position_input_set_altitude_source@Base 1.37.95 + qmi_message_loc_inject_position_input_set_gps_date_time@Base 1.37.95 + qmi_message_loc_inject_position_input_set_horizontal_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_set_horizontal_reliability@Base 1.37.95 + qmi_message_loc_inject_position_input_set_horizontal_uncertainty_circular@Base 1.37.95 + qmi_message_loc_inject_position_input_set_latitude@Base 1.37.95 + qmi_message_loc_inject_position_input_set_longitude@Base 1.37.95 + qmi_message_loc_inject_position_input_set_number_satellites_in_fix@Base 1.37.95 + qmi_message_loc_inject_position_input_set_position_source@Base 1.37.95 + qmi_message_loc_inject_position_input_set_position_source_provider@Base 1.37.95 + qmi_message_loc_inject_position_input_set_raw_horizontal_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_set_raw_horizontal_uncertainty_circular@Base 1.37.95 + qmi_message_loc_inject_position_input_set_requested_position_injection@Base 1.37.95 + qmi_message_loc_inject_position_input_set_satellites_used@Base 1.37.95 + qmi_message_loc_inject_position_input_set_speed_east_north_up@Base 1.37.95 + qmi_message_loc_inject_position_input_set_speed_uncertainty_east_north_up@Base 1.37.95 + qmi_message_loc_inject_position_input_set_time_uncertainty@Base 1.37.95 + qmi_message_loc_inject_position_input_set_timestamp_age@Base 1.37.95 + qmi_message_loc_inject_position_input_set_utc_timestamp@Base 1.37.95 + qmi_message_loc_inject_position_input_set_vertical_confidence@Base 1.37.95 + qmi_message_loc_inject_position_input_set_vertical_reliability@Base 1.37.95 + qmi_message_loc_inject_position_input_set_vertical_uncertainty@Base 1.37.95 + qmi_message_loc_inject_position_input_unref@Base 1.37.95 + qmi_message_loc_inject_position_output_get_result@Base 1.37.95 + qmi_message_loc_inject_position_output_get_type@Base 1.37.95 + qmi_message_loc_inject_position_output_ref@Base 1.37.95 + qmi_message_loc_inject_position_output_unref@Base 1.37.95 + qmi_message_loc_inject_position_response_parse@Base 1.37.95 qmi_message_loc_inject_predicted_orbits_data_input_get_format_type@Base 1.22.0 qmi_message_loc_inject_predicted_orbits_data_input_get_part_data@Base 1.22.0 qmi_message_loc_inject_predicted_orbits_data_input_get_part_number@Base 1.22.0 @@ -2456,6 +2753,21 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_loc_inject_predicted_orbits_data_output_ref@Base 1.22.0 qmi_message_loc_inject_predicted_orbits_data_output_unref@Base 1.22.0 qmi_message_loc_inject_predicted_orbits_data_response_parse@Base 1.34.0 + qmi_message_loc_inject_utc_time_input_get_time_source@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_get_time_uncertainty@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_get_type@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_get_utc_time@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_new@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_ref@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_set_time_source@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_set_time_uncertainty@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_set_utc_time@Base 1.37.95 + qmi_message_loc_inject_utc_time_input_unref@Base 1.37.95 + qmi_message_loc_inject_utc_time_output_get_result@Base 1.37.95 + qmi_message_loc_inject_utc_time_output_get_type@Base 1.37.95 + qmi_message_loc_inject_utc_time_output_ref@Base 1.37.95 + qmi_message_loc_inject_utc_time_output_unref@Base 1.37.95 + qmi_message_loc_inject_utc_time_response_parse@Base 1.37.95 qmi_message_loc_inject_xtra_data_input_get_part_data@Base 1.22.0 qmi_message_loc_inject_xtra_data_input_get_part_number@Base 1.22.0 qmi_message_loc_inject_xtra_data_input_get_total_parts@Base 1.22.0 @@ -2739,6 +3051,13 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_nas_get_drx_output_ref@Base 1.28.6 qmi_message_nas_get_drx_output_unref@Base 1.28.6 qmi_message_nas_get_drx_response_parse@Base 1.34.0 + qmi_message_nas_get_endc_config_output_get_enabled@Base 1.37.95 + qmi_message_nas_get_endc_config_output_get_immediate_scg_release@Base 1.37.95 + qmi_message_nas_get_endc_config_output_get_result@Base 1.37.95 + qmi_message_nas_get_endc_config_output_get_type@Base 1.37.95 + qmi_message_nas_get_endc_config_output_ref@Base 1.37.95 + qmi_message_nas_get_endc_config_output_unref@Base 1.37.95 + qmi_message_nas_get_endc_config_response_parse@Base 1.37.95 qmi_message_nas_get_home_network_output_get_home_network@Base 1.6.0 qmi_message_nas_get_home_network_output_get_home_network_3gpp2@Base 1.6.0 qmi_message_nas_get_home_network_output_get_home_network_3gpp2_ext@Base 1.24.6 @@ -3025,6 +3344,17 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_nas_get_tx_rx_info_output_ref@Base 1.6.0 qmi_message_nas_get_tx_rx_info_output_unref@Base 1.6.0 qmi_message_nas_get_tx_rx_info_response_parse@Base 1.34.0 + qmi_message_nas_incremental_network_scan_input_get_network_type@Base 1.37.95 + qmi_message_nas_incremental_network_scan_input_get_type@Base 1.37.95 + qmi_message_nas_incremental_network_scan_input_new@Base 1.37.95 + qmi_message_nas_incremental_network_scan_input_ref@Base 1.37.95 + qmi_message_nas_incremental_network_scan_input_set_network_type@Base 1.37.95 + qmi_message_nas_incremental_network_scan_input_unref@Base 1.37.95 + qmi_message_nas_incremental_network_scan_output_get_result@Base 1.37.95 + qmi_message_nas_incremental_network_scan_output_get_type@Base 1.37.95 + qmi_message_nas_incremental_network_scan_output_ref@Base 1.37.95 + qmi_message_nas_incremental_network_scan_output_unref@Base 1.37.95 + qmi_message_nas_incremental_network_scan_response_parse@Base 1.37.95 qmi_message_nas_initiate_network_register_input_get_action@Base 1.6.0 qmi_message_nas_initiate_network_register_input_get_change_duration@Base 1.6.0 qmi_message_nas_initiate_network_register_input_get_manual_registration_info_3gpp@Base 1.6.0 @@ -3360,6 +3690,21 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_pbm_get_capabilities_output_ref@Base 1.6.0 qmi_message_pbm_get_capabilities_output_unref@Base 1.6.0 qmi_message_pbm_get_capabilities_response_parse@Base 1.34.0 + qmi_message_pbm_get_emergency_list_output_card_numbers_element_emergency_numbers_element_get_type@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_card_numbers_element_gir_get_type@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_card_numbers@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_card_numbers_gir@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_hardcoded_numbers@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_network_numbers@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_network_numbers_gir@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_nv_numbers@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_result@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_get_type@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_network_numbers_element_emergency_numbers_element_get_type@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_network_numbers_element_gir_get_type@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_ref@Base 1.37.95 + qmi_message_pbm_get_emergency_list_output_unref@Base 1.37.95 + qmi_message_pbm_get_emergency_list_response_parse@Base 1.37.95 qmi_message_pbm_indication_register_input_get_event_registration_mask@Base 1.6.0 qmi_message_pbm_indication_register_input_get_type@Base 1.6.0 qmi_message_pbm_indication_register_input_new@Base 1.6.0 @@ -3687,6 +4032,38 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_pds_set_gps_service_state_output_ref@Base 1.6.0 qmi_message_pds_set_gps_service_state_output_unref@Base 1.6.0 qmi_message_pds_set_gps_service_state_response_parse@Base 1.34.0 + qmi_message_qos_bind_mux_data_port_input_get_direction@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_get_endpoint@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_get_mux_id@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_get_type@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_new@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_ref@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_set_direction@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_set_endpoint@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_set_mux_id@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_input_unref@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_output_get_result@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_output_get_type@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_output_ref@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_output_unref@Base 1.37.95 + qmi_message_qos_bind_mux_data_port_response_parse@Base 1.37.95 + qmi_message_qos_bind_subscription_input_get_subscription_id@Base 1.37.95 + qmi_message_qos_bind_subscription_input_get_type@Base 1.37.95 + qmi_message_qos_bind_subscription_input_new@Base 1.37.95 + qmi_message_qos_bind_subscription_input_ref@Base 1.37.95 + qmi_message_qos_bind_subscription_input_set_subscription_id@Base 1.37.95 + qmi_message_qos_bind_subscription_input_unref@Base 1.37.95 + qmi_message_qos_bind_subscription_output_get_result@Base 1.37.95 + qmi_message_qos_bind_subscription_output_get_type@Base 1.37.95 + qmi_message_qos_bind_subscription_output_ref@Base 1.37.95 + qmi_message_qos_bind_subscription_output_unref@Base 1.37.95 + qmi_message_qos_bind_subscription_response_parse@Base 1.37.95 + qmi_message_qos_get_bind_subscription_output_get_result@Base 1.37.95 + qmi_message_qos_get_bind_subscription_output_get_subscription_id@Base 1.37.95 + qmi_message_qos_get_bind_subscription_output_get_type@Base 1.37.95 + qmi_message_qos_get_bind_subscription_output_ref@Base 1.37.95 + qmi_message_qos_get_bind_subscription_output_unref@Base 1.37.95 + qmi_message_qos_get_bind_subscription_response_parse@Base 1.37.95 qmi_message_qos_get_flow_status_input_get_qos_id@Base 1.22.0 qmi_message_qos_get_flow_status_input_get_type@Base 1.22.0 qmi_message_qos_get_flow_status_input_new@Base 1.22.0 @@ -3776,6 +4153,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_tlv_read_sized_guint@Base 1.12.0 qmi_message_tlv_read_string@Base 1.12.0 qmi_message_tlv_write_complete@Base 1.12.0 + qmi_message_tlv_write_gdouble@Base 1.37.95 + qmi_message_tlv_write_gfloat@Base 1.37.95 qmi_message_tlv_write_gint16@Base 1.12.0 qmi_message_tlv_write_gint32@Base 1.12.0 qmi_message_tlv_write_gint64@Base 1.12.0 @@ -4178,6 +4557,25 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_uim_verify_pin_output_ref@Base 1.14.0 qmi_message_uim_verify_pin_output_unref@Base 1.14.0 qmi_message_uim_verify_pin_response_parse@Base 1.34.0 + qmi_message_uim_write_record_input_get_file@Base 1.37.95 + qmi_message_uim_write_record_input_get_response_in_indication_token@Base 1.37.95 + qmi_message_uim_write_record_input_get_session@Base 1.37.95 + qmi_message_uim_write_record_input_get_type@Base 1.37.95 + qmi_message_uim_write_record_input_get_write_record@Base 1.37.95 + qmi_message_uim_write_record_input_new@Base 1.37.95 + qmi_message_uim_write_record_input_ref@Base 1.37.95 + qmi_message_uim_write_record_input_set_file@Base 1.37.95 + qmi_message_uim_write_record_input_set_response_in_indication_token@Base 1.37.95 + qmi_message_uim_write_record_input_set_session@Base 1.37.95 + qmi_message_uim_write_record_input_set_write_record@Base 1.37.95 + qmi_message_uim_write_record_input_unref@Base 1.37.95 + qmi_message_uim_write_record_output_get_card_result@Base 1.37.95 + qmi_message_uim_write_record_output_get_response_in_indication_token@Base 1.37.95 + qmi_message_uim_write_record_output_get_result@Base 1.37.95 + qmi_message_uim_write_record_output_get_type@Base 1.37.95 + qmi_message_uim_write_record_output_ref@Base 1.37.95 + qmi_message_uim_write_record_output_unref@Base 1.37.95 + qmi_message_uim_write_record_response_parse@Base 1.37.95 qmi_message_unref@Base 1.6.0 qmi_message_voice_answer_call_input_get_call_id@Base 1.14.0 qmi_message_voice_answer_call_input_get_type@Base 1.14.0 @@ -4528,6 +4926,17 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_wds_bind_mux_data_port_output_ref@Base 1.18.0 qmi_message_wds_bind_mux_data_port_output_unref@Base 1.18.0 qmi_message_wds_bind_mux_data_port_response_parse@Base 1.34.0 + qmi_message_wds_bind_subscription_input_get_subscription_id@Base 1.37.95 + qmi_message_wds_bind_subscription_input_get_type@Base 1.37.95 + qmi_message_wds_bind_subscription_input_new@Base 1.37.95 + qmi_message_wds_bind_subscription_input_ref@Base 1.37.95 + qmi_message_wds_bind_subscription_input_set_subscription_id@Base 1.37.95 + qmi_message_wds_bind_subscription_input_unref@Base 1.37.95 + qmi_message_wds_bind_subscription_output_get_result@Base 1.37.95 + qmi_message_wds_bind_subscription_output_get_type@Base 1.37.95 + qmi_message_wds_bind_subscription_output_ref@Base 1.37.95 + qmi_message_wds_bind_subscription_output_unref@Base 1.37.95 + qmi_message_wds_bind_subscription_response_parse@Base 1.37.95 qmi_message_wds_configure_profile_event_list_input_get_register@Base 1.34.0 qmi_message_wds_configure_profile_event_list_input_get_register_gir@Base 1.34.0 qmi_message_wds_configure_profile_event_list_input_get_type@Base 1.34.0 @@ -4638,6 +5047,12 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_wds_get_autoconnect_settings_output_ref@Base 1.14.0 qmi_message_wds_get_autoconnect_settings_output_unref@Base 1.14.0 qmi_message_wds_get_autoconnect_settings_response_parse@Base 1.34.0 + qmi_message_wds_get_bind_subscription_output_get_result@Base 1.37.95 + qmi_message_wds_get_bind_subscription_output_get_subscription_id@Base 1.37.95 + qmi_message_wds_get_bind_subscription_output_get_type@Base 1.37.95 + qmi_message_wds_get_bind_subscription_output_ref@Base 1.37.95 + qmi_message_wds_get_bind_subscription_output_unref@Base 1.37.95 + qmi_message_wds_get_bind_subscription_response_parse@Base 1.37.95 qmi_message_wds_get_channel_rates_output_get_channel_rates@Base 1.20.0 qmi_message_wds_get_channel_rates_output_get_result@Base 1.20.0 qmi_message_wds_get_channel_rates_output_get_type@Base 1.20.0 @@ -5214,6 +5629,20 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_wms_delete_output_ref@Base 1.6.0 qmi_message_wms_delete_output_unref@Base 1.6.0 qmi_message_wms_delete_response_parse@Base 1.34.0 + qmi_message_wms_get_broadcast_config_input_get_message_mode@Base 1.37.1 + qmi_message_wms_get_broadcast_config_input_get_type@Base 1.37.1 + qmi_message_wms_get_broadcast_config_input_new@Base 1.37.1 + qmi_message_wms_get_broadcast_config_input_ref@Base 1.37.1 + qmi_message_wms_get_broadcast_config_input_set_message_mode@Base 1.37.1 + qmi_message_wms_get_broadcast_config_input_unref@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_config_channels_element_get_type@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_get_config@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_get_config_gir@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_get_result@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_get_type@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_ref@Base 1.37.1 + qmi_message_wms_get_broadcast_config_output_unref@Base 1.37.1 + qmi_message_wms_get_broadcast_config_response_parse@Base 1.37.1 qmi_message_wms_get_message_protocol_output_get_message_protocol@Base 1.6.0 qmi_message_wms_get_message_protocol_output_get_result@Base 1.6.0 qmi_message_wms_get_message_protocol_output_get_type@Base 1.6.0 @@ -5235,6 +5664,27 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_wms_get_supported_messages_output_ref@Base 1.14.0 qmi_message_wms_get_supported_messages_output_unref@Base 1.14.0 qmi_message_wms_get_supported_messages_response_parse@Base 1.34.0 + qmi_message_wms_get_transport_network_registration_status_input_get_type@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_input_new@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_input_ref@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_input_unref@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_output_get_registration_status@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_output_get_result@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_output_get_type@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_output_ref@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_output_unref@Base 1.37.95 + qmi_message_wms_get_transport_network_registration_status_response_parse@Base 1.37.95 + qmi_message_wms_indication_register_input_get_report_transport_network_registration_status@Base 1.37.95 + qmi_message_wms_indication_register_input_get_type@Base 1.37.95 + qmi_message_wms_indication_register_input_new@Base 1.37.95 + qmi_message_wms_indication_register_input_ref@Base 1.37.95 + qmi_message_wms_indication_register_input_set_report_transport_network_registration_status@Base 1.37.95 + qmi_message_wms_indication_register_input_unref@Base 1.37.95 + qmi_message_wms_indication_register_output_get_result@Base 1.37.95 + qmi_message_wms_indication_register_output_get_type@Base 1.37.95 + qmi_message_wms_indication_register_output_ref@Base 1.37.95 + qmi_message_wms_indication_register_output_unref@Base 1.37.95 + qmi_message_wms_indication_register_response_parse@Base 1.37.95 qmi_message_wms_list_messages_input_get_message_mode@Base 1.6.0 qmi_message_wms_list_messages_input_get_message_tag@Base 1.6.0 qmi_message_wms_list_messages_input_get_storage_type@Base 1.6.0 @@ -5359,6 +5809,33 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_message_wms_send_from_memory_storage_output_ref@Base 1.6.0 qmi_message_wms_send_from_memory_storage_output_unref@Base 1.6.0 qmi_message_wms_send_from_memory_storage_response_parse@Base 1.34.0 + qmi_message_wms_set_broadcast_activation_input_get_activation@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_input_get_type@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_input_new@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_input_ref@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_input_set_activation@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_input_unref@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_output_get_result@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_output_get_type@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_output_ref@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_output_unref@Base 1.37.1 + qmi_message_wms_set_broadcast_activation_response_parse@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_channels_element_get_type@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_get_channels@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_get_channels_gir@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_get_message_mode@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_get_type@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_new@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_ref@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_set_channels@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_set_channels_gir@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_set_message_mode@Base 1.37.1 + qmi_message_wms_set_broadcast_config_input_unref@Base 1.37.1 + qmi_message_wms_set_broadcast_config_output_get_result@Base 1.37.1 + qmi_message_wms_set_broadcast_config_output_get_type@Base 1.37.1 + qmi_message_wms_set_broadcast_config_output_ref@Base 1.37.1 + qmi_message_wms_set_broadcast_config_output_unref@Base 1.37.1 + qmi_message_wms_set_broadcast_config_response_parse@Base 1.37.1 qmi_message_wms_set_event_report_input_get_new_mt_message_indicator@Base 1.6.0 qmi_message_wms_set_event_report_input_get_type@Base 1.6.0 qmi_message_wms_set_event_report_input_new@Base 1.6.0 @@ -5421,6 +5898,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_nas_hdr_personality_get_type@Base 1.6.0 qmi_nas_hdr_protocol_revision_get_string@Base 1.6.0 qmi_nas_hdr_protocol_revision_get_type@Base 1.6.0 + qmi_nas_incremental_network_scan_status_get_string@Base 1.37.95 + qmi_nas_incremental_network_scan_status_get_type@Base 1.37.95 qmi_nas_lte_band_preference_build_string_from_mask@Base 1.6.0 qmi_nas_lte_cell_access_status_get_string@Base 1.24.6 qmi_nas_lte_cell_access_status_get_type@Base 1.24.6 @@ -5536,6 +6015,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_oma_session_state_get_type@Base 1.6.0 qmi_oma_session_type_get_string@Base 1.6.0 qmi_oma_session_type_get_type@Base 1.6.0 + qmi_pbm_emergency_number_flags_build_string_from_mask@Base 1.37.95 + qmi_pbm_emergency_number_flags_get_type@Base 1.37.95 qmi_pbm_event_registration_flag_build_string_from_mask@Base 1.6.0 qmi_pbm_event_registration_flag_get_type@Base 1.6.0 qmi_pbm_phonebook_type_build_string_from_mask@Base 1.6.0 @@ -5581,6 +6062,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_slot_eid_element_get_type@Base 1.32.0 qmi_ssc_report_type_get_string@Base 1.34.0 qmi_ssc_report_type_get_type@Base 1.34.0 + qmi_subscription_type_get_string@Base 1.37.95 + qmi_subscription_type_get_type@Base 1.37.95 qmi_uim_card_application_personalization_feature_get_string@Base 1.10.0 qmi_uim_card_application_personalization_feature_get_type@Base 1.10.0 qmi_uim_card_application_personalization_feature_status_get_string@Base 1.34.0 @@ -5844,6 +6327,8 @@ libqmi-glib.so.5 libqmi-glib5 #MINVER#, libqmi-proxy qmi_wms_message_type_get_type@Base 1.6.0 qmi_wms_notification_type_get_string@Base 1.6.0 qmi_wms_notification_type_get_type@Base 1.6.0 + qmi_wms_nw_registration_get_string@Base 1.37.95 + qmi_wms_nw_registration_get_type@Base 1.37.95 qmi_wms_receipt_action_get_string@Base 1.6.0 qmi_wms_receipt_action_get_type@Base 1.6.0 qmi_wms_storage_type_get_string@Base 1.6.0 diff --git a/packaging/libqmi/debian/upstream/signing-key.asc b/packaging/libqmi/debian/upstream/signing-key.asc index 1310c2b..d70a260 100644 --- a/packaging/libqmi/debian/upstream/signing-key.asc +++ b/packaging/libqmi/debian/upstream/signing-key.asc @@ -90,3 +90,138 @@ g3cO7H5+wW3dks6wOEBmeEuAi38wsG412e3LnkWLRFjyntenXZVDnMXuyDCG7Pn0 MGUY =+Ikk -----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFncZh4BEACwYEzRJYSVv0smRae5QOQUNS+0HUoesyTRusvE8/JfQtZpZYra +YUcDdnPMPwYiH0QckkhN7XDpDsn5WZtKIbzLfnqx2oOnJA/i9Rj4vqgt6JRCu4IA +w32sJQZVDe5UzF9jlIjstEXSrIGZ1cTlTFHbIlirdxPdpRsRkofakk/kMmO7b8vH +28u4XFdNOuDUUeDgnTdLV6p1r9EeWi3A0qKp7YeN2o9FL0Cw7DFu0Av8lQJIXGgw +CbXqVjoPu1gg3BFbm8g9GYkI4ibUaHeptQLmSQM1N5McOve2LGwO63MjkMFBnowH +kqGETG9RqlNGIl8DcbldlmLNQv57X/OYqsYGZ02h6eno3glfoau5EvSBTFbPdrSA +Mze8CbezF9I5rcswHhWLhi/h222SPKEaewtkPKIG+vIJnSFnIyqO7Js/Ze26XV9O +HjbukwyYxK3PPDt1gsUW0LMZox6TOaxcAFdbFbey1bo4yDeBMbwa40rxc3pVr83K +ao2m0LMtc0IO3QDw+n2Z1+h+X3HBdDYCt6HCqLzkkE2DoZy2NJ+MifxeftJIcvyz +aJwy46vtSxJokRDedKxlUJkvxoDzkl/cR0ffNCKzJ4UQyolzom8HdbwPnIFXguSk +Nh1lyUao0flkGjjHjnpJ6PVlVPVXEUMb2IV9+/yIYvNBSvCLtQam9t4rqwARAQAB +tB5HdWlkbyBHw7xudGhlciA8Z3VpZG9AeGNwdS5jaD6JAk4EEwEKADgWIQQNs5Mn +YveOWS9lIq+7Wix3WEEi0wUCWdxoHwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIX +gAAKCRC7Wix3WEEi00BkD/9MkayyZT6RVO3J/aAfS9uuAH3Kk49k1/Ecvml6lXnv +Z3Joo+oEXmVSTzwwpUgBzMO357C5Wr0DqstQVGHOh31b3ek8AGtCMUqnuFNniMfC +a/wWlIgvJUxPxVbbZfHbOLqBW73w3l5Vq7TEqz+xX4ge/+aQArI0EN7DU5zJvsrO +z7AWUL8QBHJlRRi6WuAbPnP8vs9bncZUU61ws+z2P61eAs9lynz//3UzAnP1/hVi +BHAaJKagqfuT5oinzFHERkOy7fZsTdI3ZiOzxbNQBevw502a/nZFJn0ZcN06jKY5 +7m+qPf6QKYkFhHU6xwBuli7yTAuZtnKth0ffI5bVcDIkYziPIp2I7ofe4szqpq5H +rMF96UdlGTd/SGO/4WN7MD+/Svs5VP9xEoJpnxEMK0kda9xq3JIBscWs+V62bio8 +d/jc3a4Ab3hv9UDHUjGhI66y220Rgs3AUJFoGAAR608II2KmayI4XM5GBgr2LgmE +Zi9/ow/t7rFXx4kszsLraLpjMVE5vcZvqQQrVuZiAGtL8XJHcQ9FyPri1LH6Gs7R +jF1hmtC66wxwy7puyyvzzprw+r+husMpeiuugBQZoXcAY1oqhSvOTi/Gc+9G+fE1 +7v5G7fvC+V7xjruzd5D9xk9QvwAEE5OVzaPDmkl2b8t9qT1HVgjduuOCfHqfDx/H +TrQcR3VpZG8gR8O8bnRoZXIgPGFneEB4Y3B1LmNoPokCTgQTAQoAOBYhBA2zkydi +945ZL2Uir7taLHdYQSLTBQJZ3GYeAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA +AAoJELtaLHdYQSLTAzsP/3eJxK+UmwxAoT2pg33JLxSltlfcyuBqbsfAtF9qFhVO +WIo4bOMG982fdlgcR5GexPzT73ouni89ETtD5PYqH+cVuPADjNefJMn5RCn+bIdu +n3Z0yhNqsWDsbR0hOlWB/5TkEEm6ziVehWYFRnNpUqFoqMpJWkyd77NZd3RjB/IE +IzkOvgcIpPLQPiDPnzO4v/B5WR1YVTx7dGpHQK4fpqK86QXVFe5rCJmc6RRGdiiG +TnDM7Gpl9UpJ0P7hkf5V42VjSXLohLotsNGKbYWFna7yUj1iv75nVrUh5uEg6qef +nub2B+CCYG3iO/xbs5bxj+gFdjxJuTCKabgA20jalXFKPciVm9j8IN4j/2z4xqW+ +9/fuu85S4og6XMD8Hw/F6XeVS4DlNacZbUw9cC9Ykb2uDxSnABXam7tghj4Cukxv +LcioisLdWR6jSWOe1CYGoyHwpp8p3UFfDOt7T4MdwomtVfsjZp+qGPxIwYU8IGmd +HDpKs8MQpFvdYITxXinv+6f0OWiw9+smOVA3F5OdfOom9a+sMxOdFJ2DjAp1c2dn +N+zRNDQTagWiI5grpNUaW0ENws6voUfqio8YgYnWb5vKxS5d+Ghc5YR8W6wJCCV6 +aXAjSjwXhXDLo6dx8lc1stTy5AQjWgF/0Vl84gSTpJqm95jKXLfKhFMRJ5ULMQhY +tCBHdWlkbyBHw7xudGhlciA8YWd4QHNpZ3hjcHUub3JnPokCTgQTAQoAOBYhBA2z +kydi945ZL2Uir7taLHdYQSLTBQJZ3Gf5AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B +AheAAAoJELtaLHdYQSLTWbwQAKvPpEHqGb+QSQvWm0pWye+yiLOfUbWDwIGblBCz +xIfDJhLCDhsRb3wDq4YS06zcxoLRWa3nD5XasvUEtGXBq7az1J3i5t4vjhoH3vjx +ZXnchAUuIJ7KWlYbcG2BSYyhVS7sANjSwwVFD35fQz8tvsI5fbiKt/9rKlXnYkdT +YnWtaNfG2IIhIfG/Fiyl0a9IBcIDNiJqXCb35D3zMei9f4i6G3Ji6jayu+2CuJC8 +mxthZgZt7pEeVmaNknB9/WvnJHLYZSMOlqUU4OCLAXaQot3VtRJMl+stRqGUsL8l +2BRxkIGyma6xsVIdm6FIvVt1WGRRi6GF43rXnKeh9GcQE2k4MzLRuR0EXAWxx1zz +L6G746dnReFm3H6J8hZZAv1aApK2F8HmVRxXRhsczFz+TrgSsCew/Fvr+4XRDt+b +jZZT0Kx/bndC824Ti/j+GB/Bt8RX/tBHjeS5EQDcWlz/eEHKGjVWeNeTwisb6PNB +gsib//UrkKUMueWgAs9QwWXbV91BQdy84iV/cyzQz2bQ1bPd/uvZX8NIa4pWF3mi +FFXUrlym5kUYbYh7qT2Hv723oemEjIeNYuf6BH5ha/PsAovkqgWZ4PaeB9U5w+v1 +yHDG7cCs6RP+X8D84oLQBr4wZs/+giNN2WM6DHyIT9J0AfiNe0BRw9b52JWi2JOP +MfrpuQINBFncZh4BEADN7hJVdYI1d6SCjW5+hdNxC/rJXssFjU3QNRdH8TOyOnTX +B7pqnh+LZN9qmoajkh/R+rqX0BUQkDot4CVvfri9rwNNr3jNRIx6qYcq78mwiNw3 ++Ito9AgP2egkpwfHK0Itbi3kVKhy08zS3NBUfT/WPAjRNHeiLmQPHlRXz+p54qrJ +L2Cv2MNMINk6WGeSaMZuDHpQ8+3IFW8d3LLpaJlzSLQ9lRXzLh2ahiLjYOe+T2WF +NfCuKLx2xDw+wtGKTr37Ik7ClqF4cDeBGYmf8cYIh7HnP9hqUGaqHUrXPypnkPO/ +E/VSp8GRjyvJxS5Ltpa8oLWyk80i5nuswt108edmrbq6y0N9htWD37NKcNJJ+8xT +O+1vyUmqVbJ5TqpeoDlLVxhiBCz5LIWy6xzR4cqjCDK670GxQocxei4AdOAKzM5w +G9DYe6MJVc3pjdu+TNnrPCFaLV1dHCbbfFBgQqC+QyCgKMlO8O0SbRg2XquMqtC7 +aoh/pyYI1len+aqFLIuz6bTZGeK+tVkLNCn+80mhReK8mcw4fNgRG6x8RvOVzqfR +TeHFrh7EKLbdwizdde7BX9WOxfxm6Z5eMXk/yABc3GdUol5tHGCIXJYkWf+TlUA4 +lTL9BISOvC/uodFOGZpDWHv5DZ+bMJSNtrG93Z00H+hEaDOtAtZDmEU6BhnoNQAR +AQABiQI2BBgBCgAgFiEEDbOTJ2L3jlkvZSKvu1osd1hBItMFAlncZh4CGwwACgkQ +u1osd1hBItO1yQ//ZOZi7E/XTshotE8d4WNYjqrXJ4e0MaVQbRfNEOCP+bIeCNjz +e8Hl7FwSAV21c8CzAN71jGH6Z6/yGfnjHNXPXiqemWs5bYW2RXBJYfBvo4GifLBM +T+IIiBxfT0s2VOyjwsiv5aPvicVGEcxf9FZHLBj8LyrJE29uOpEiRBBPfDuMCQmF +jxZ2fbA1XTy6EOYY0p/Q2tV/5hJcaYRkhvDSkQG+Z7DqcLhvbNTq6kRCnkUgvHCe +J5YMUZHc9U0Cp2wkUlvHZ29bbvQMysAAv2NnSrBcU7RnU55RHdHEfYg0BG2iybx/ +C8cR4zZye4fvs1NUnv/OjBHBHrSr+NNi2PpvtRfCUaf5HcMNZz6bDzhStPPlhNpX +01H0nmHmmEik/0FgjxXgnS5hYoWbtiwdInjfZjgpaB/tnDhKufrA/xGataBiGYjB +mT6jZYonGjDkc3r1BPSgch/cHeMq9mN5DHv65XGteosNLsFo4XqrabZGDxF7XkZl +PGNMVR26Z00JkOOEhDcXeAVNIb3LKnhYzmh89/Qqg7t4+tz0niBlmJ2IxooOOjoA +9YooAxZpkRD1tLp+fUaT+Bx4aPpmwkM4ShZtjde3Ufmn2xtqwTzuyWGx/S5zOoaA +VtQ8liI/sJASD66PBHzMtBd6iJy+k+LwPmab8xJR44bwZhd6zBTjyE+aKdy5Ag0E +WdxoNQEQAMi+oiqQGUYzjpFJu46HilzyS4v72oNi3bb1Fa9ckLliaEq4uRbROaEz +5zgDnJCfUu6Cc7K5bicCuNfRuWCVewRfzhQXU9mWI6pKU02vitLBDmd7OF+B2VdO +/UcdtOXxZsb/D6f6Obj9RAKnBQxWr6c830kot7j2o5bHI10XyT4Bd3E/G7yzayiR +JFA0jdpOU6xa1Gd58Ubl0RyUD3RPy4+rLbl4tu+FAtZvD2126q4fMzlNMVUkS+pG +IWjajgzuBpf9SIrBr6WzMLZc5gQ+jXIsF+lKJQQ+tBIdD6RdhJ2cnyY11rW4OuZE +6XAZP0U+IytWFzFHc4B3p6FEXKlmu6eAulxmAwVt1Vkj2Xg461rqHfcLUYZFLazq +hPoxv5XZc6OoaMzofYjr894kJjNNP0jxh041mu4uZX56lqP6O0QMKhBVi7o2qp7+ +QW8Wehnhn2/+PpPNnkuzxouIzPuUOlpgLDX9n/Vra+F70A+YbvrI+O8764Vi8k92 +jmJm16nVB8wtfyw5SH2xX7ol6n2hIy9kxr0y6oOzIPsp1fETr2zo8zCBucICV3fg +svvnPvOfXOGaypLLCxKrECIbnccNOc3XwVnUrQGXAtsyj6v0Lp9568mx9dTlZf0l +XLIDlxQBBerxOQmoFsbKxVCot7nwRBGKU0RHgiaqoK39jNYPkmwBABEBAAGJBHIE +GAEIACYCGwIWIQQNs5MnYveOWS9lIq+7Wix3WEEi0wUCZS+u0AUJDxWtmwJAwXQg +BBkBCgAdFiEEY/bM35YinQkoayrDJb+GUkr8weMFAlncaDUACgkQJb+GUkr8wePU +7g//Wp5qj+iNe4X74Tuw3eGEAAH6kHSAp0D0dUQeUJTicO/outAtpaVkYbDeyHS8 +bhu5tQAu9d5c6czfWI4d2sPsYmOdnAnLpZDDPJeasUbJzeNI50CgLslqGotOH+Km +5B7ZSKzcUr/kVHDJDm81G6QDgveDxLwpfCgSQKsByO16DKgGrzj66x7FrbAKeJ31 +2P+f3TppAA71PZ+IYIVGZgcBWZa+FUy9UDo9DnGGU1pGsbzl/tmD2D+d57ofidVF +X5YQh7HjfDliCxphmAW72VnPfmDF9Xt8WAAGlz8EkpIqnFlVEeW6Zwpb1k/FrPPF +lqGtNHL54w45cUtqpEGYaSGM1FBgcs29+DFYdqwX5BhHx+1uZCNFESrn8TdDwSkM +9ijdWqI8RYJiaynkRKnel0scEeH0ZojbQQR5w3aj9tEWDE8swKJFqHAcKk4qoFq/ +OJkjqD41Yh8bDNFAXWHNBgJGMdhlfTq9gHRSmw8eSgRCvBV737kYlOjsyr4lzNm8 +dXjwj2tqPg2NWbL3Uk0t0Kq3vKX5bHWE4Bi29BfMGyUfhWDa+IWCkQe7Q9Ynq0gH +hgTaJoMLGHbAvaujTUGd60TN1W5pmhkKI0DbHqJ8sRCexlxrZ9qGfdnfa9W/Q8pH +uVvXJ6pQN8tVBL4fccLAuaBV+VHmGjeSoW2UeiM1xG0CuxoJELtaLHdYQSLTgVoP +/RWwQiAbyN6QmCBhjmxXHzwS+SoE7uqbELl8eO5IMSVUaIBGO1HEz1ZKj4hvyxo3 +hNo4oTKhJP9bPXzsHJdt04S4hu/33SS6zSK6VQ+mArG+ocK2sEhdqdKQX31Gjmeg +FXa80jRkUNzjC2LnaxyTrG5zKkqMZJFXkjweNulbGfGYR7SNj2iXCvm8ddjWuIqr +uAvikrCh87vUZpYXynkfCzMHb8GJwae/NB1KYCVkwF6egxLgIYX+EYzPtlnDXnWX +1d4pkcRjwoniwbtew15+55DOavetZtUpR2swDzo5jghOwr9DAyQ4pJ+YPhvZ03lg +i3mTFroG+GRtgipSdoVxg6W8kFFnDHewRu+hbJQ/LfvNngGl1tR9gSqjb47nkg/v +sSwRh0f4hTM9iqnb2sT+5x1UOyyBHuRV3GMDdueqkOKTkOo1mybcbyJcKdanQrsp ++832GEMmW25nweNjCNp2g1gUc+gunkW+3Qg3uhg9Gf0zNEUVq8zgqtfnWTsK3qTi +93tCXrzGXV6F/IZdYXpAbOdeuF6QmUgJpQ7iaeKcRyNuTwDI6HDnbU3PLRzwM0QI +EGumOjklHe/0kGx78XO+co/fnQB06L3X6ZWuD1wcoNTa1FkDmScqZQRwmR0T2Yx9 +MXa9lscRvxIOgTmhMKyWv5vtrHjqLWbmoKwEM2UjWSwouQINBFnfSLgBEADndvl7 +are+0D2mKExDfo+s6gsgBpSoYP47Yv92btvvTe2nywO2uAFe2sY5aeJNx/E0VffA +y2X1KtkXMsS3l8yWAt38w4j+4yleSN8t0nlH5gpa1VSUWUZopa1IuBN6aqWny4HK +niwHi1zjD18BcuHEvAl0+P34cUGouffNVdEuyyVBlu6ZxeHUQCjJbEI/0MqLobq7 +VFtrdtZgDjPuGn/4exa/1sON8VIzAfg8tf9C80cxPTu98+y6H+ZErFaKEOEd5jaM +6bM68YdgVmbD/BEulgSS2Zp7CHGjUjIvNh/OU072xvKMHtmzhq4hHhiwFGeClT3f +kU1ROZtg8runOzvsvlIM7hDsU8+aV/OL56w67DVIUrrOkKMtukNUtfyU1SeOUx52 +OyCJQmR0UD0gnm9UDNCG7vhUT+1GglVjmHyIE2Kq+P3eugZyF9JnVrmvZ0ubXWKU +fLLFSwv8g5ud3kwa0RTlK1PIjPMZ48xmXY+n0pQSPfMhVvt5xR4cv+pbUGL9M9AV +q+rU8+A+WCBm+neRc0+lPJyoSKwqNJD/DRBuiBzOAD+L4RQCbJ+458qMWpTStTIc +dfDm/ZZiUpBv6+So/W8clP3YqdAHNbYYbMcXoK8AA2uxppQ8VNB4gCB/f7FuwpGr +NtMHwGawpC6bm3hAcY0f4ANEJJwFPKCoZ7hIIwARAQABiQI8BBgBCAAmAhsMFiEE +DbOTJ2L3jlkvZSKvu1osd1hBItMFAmUvrtkFCQ8SzRgACgkQu1osd1hBItP/DQ/7 +Bnen2X5F/c0QCORKR0moEw4tZjnsrYSrEhie8yPVZyWIUTpKuSan3GhTE7+6qu4/ +idLhLgSvXru8jqWZaO55iSqBm0mCY71l7hcd5LhZb71w9kXmyQtARTi7uDD+/qAc +jJJp0niHOINrsUO/qWaS0q9NfvmYwQGKtd4jjzKmISVWi2ZDi4OA+/S8nw4oaGmY +zS9pXKYsaJFQDC5qXUXFdcUTJoKGltaQZIonnYhtL8MaMEGyXadDb0YMhuJrfh5k +IrmVlHunAA/SXgK2+ldgJhUFdSzHiIon39TUMUXmPueGTuijyrhAT9NQO5qOr8wR +4s793P8PRb9Ix/P8RjTseL7gjM7CH+jayZ9/pxEiDgYFTzFDga5SSBuzRE5hhduO +B1LB3FfU4zO8QXGn1AYYqIhwqzlKrHsSZT4cpzycXEuBovQKgfBwGkfZBgq7ZOlo +rwG6DlQ4NTkyOKA4XM8BieF+ZnjdVRuHJmj2ApuSAXGdO29in39mCjRbWcDxNcrl +v4llJhc8k/pPUKrOswKGo7QPTY6TB1T+yLGvyZmhzZ2fK1VTNAN6FkhIlTTY+gL2 +QzsVV4oMiz2Jk767uW8TG30Wds5CxlsEUTbEV/gZkamBQzq5PbnTh6mR/V5rAV4D +QroE4Y241+no2wNczXvE/4bo6aBw0z2mahXiqebLLPE= +=jlqw +-----END PGP PUBLIC KEY BLOCK----- diff --git a/packaging/libqmi/debian/watch b/packaging/libqmi/debian/watch index 86a2bc4..62fe308 100644 --- a/packaging/libqmi/debian/watch +++ b/packaging/libqmi/debian/watch @@ -1,4 +1,5 @@ -version=4 -opts="mode=git,pgpmode=gittag,uversionmangle=s/-(alpha|beta|rc)/~$1/" \ - https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@.git \ - refs/tags/(\d+\.\d?[02468][\.\-].*) +Version: 5 + +Template: Gitlab +Dist: https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@ +Matching-Pattern: .*(\d+\.\d[02468](\.\d*)?(\-(alpha|beta|rc))?\d*) diff --git a/packaging/libqrtr-glib/debian/changelog b/packaging/libqrtr-glib/debian/changelog index 415f9f9..dda3e15 100644 --- a/packaging/libqrtr-glib/debian/changelog +++ b/packaging/libqrtr-glib/debian/changelog @@ -1,3 +1,43 @@ +libqrtr-glib (1.4.0-1) unstable; urgency=medium + + [ Sebastien Bacher ] + * debian/tests: include a basic autopkgtest for the library + + [ Guido Günther ] + * New upstream release + * Switch to gi-docgen + + [ Arnaud Ferraris ] + * d/watch: drop custom matching pattern. + This was supposed to be clever (avoiding reporting dev versions), but it + actually reports a wrong version number, so let's just drop it and + revert back to default behaviour. + + -- Guido Günther Thu, 08 Jan 2026 18:55:34 +0100 + +libqrtr-glib (1.2.2-2) unstable; urgency=medium + + * d/gbp.conf: switch Debian branch to debian/latest. + Also add a few more defaults (copied from libqmi) for consistency. + * d/watch: convert to version 5. + Use the new Gitlab template for easier management, but override the + matching pattern so we only get stable (pre)releases, which have an even + minor version number. (Closes: #1081060) + * d/control: modernize build dependencies + `libgirepository1.0-dev` shouldn't be used anymore as it isn't + multiarch-friendly. Instead, use a recent `gobject-introspection` and + explicitly (build) depend on the needed `gir1.2-*-dev` packages. + Similarly, `pkg-config` has been deprecated in favor of `pkgconf`. + While at it, drop the now-obsolete `Rules-Requires-Root` and bump + `Standards-Version` as no other change is needed. (Closes: #1118881) + * debian: drop now-unneeded lintian overrides + * d/copyright: fix small issues. + One of the `meson.build` files is mentioned with a wrong path. Moreover, + the GPL-2+ text still mentions the (old) FSF street address, replace it + with the usual reference to http://www.gnu.org/licenses/. + + -- Arnaud Ferraris Tue, 28 Oct 2025 13:03:28 +0100 + libqrtr-glib (1.2.2-1) unstable; urgency=medium * New upstream version 1.2.2 diff --git a/packaging/libqrtr-glib/debian/control b/packaging/libqrtr-glib/debian/control index 08f26c9..ea2fcdd 100644 --- a/packaging/libqrtr-glib/debian/control +++ b/packaging/libqrtr-glib/debian/control @@ -1,21 +1,21 @@ Source: libqrtr-glib Priority: optional Maintainer: DebianOnMobile Maintainers -Uploaders: Arnaud Ferraris , +Uploaders: Arnaud Ferraris , Guido Günther , Henry-Nicolas Tourneur , Martin Build-Depends: debhelper-compat (= 13), + dh-sequence-gir, gobject-introspection, - gtk-doc-tools, libgirepository1.0-dev, libglib2.0-dev (>= 2.56), meson, - pkg-config, + pkgconf, python3:any, -Build-Depends-Indep: libglib2.0-doc -Standards-Version: 4.6.0 -Rules-Requires-Root: no +Build-Depends-Indep: gi-docgen , + libglib2.0-doc +Standards-Version: 4.7.2 Section: libs Homepage: https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib/ Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib.git @@ -24,7 +24,6 @@ Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/libqrtr-glib/ Package: libqrtr-glib0 Architecture: linux-any Multi-Arch: same -Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Support library to use the QRTR protocol @@ -38,8 +37,9 @@ Architecture: linux-any Multi-Arch: same Depends: libglib2.0-dev, libqrtr-glib0 (= ${binary:Version}), - gir1.2-qrtr-1.0 (= ${binary:Version}), + ${gir:Depends}, ${misc:Depends}, +Provides: ${gir:Provides} Suggests: libqrtr-glib-doc, Description: Development files for libqrtr-glib glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. @@ -51,7 +51,6 @@ Section: doc Architecture: all Multi-Arch: foreign Depends: ${misc:Depends}, -Suggests: devhelp, Build-Profiles: Description: API documentation for libqrtr-glib glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. @@ -62,9 +61,7 @@ Package: gir1.2-qrtr-1.0 Section: introspection Architecture: linux-any Multi-Arch: same -Depends: ${gir:Depends}, - ${shlibs:Depends}, - ${misc:Depends} +Depends: ${gir:Depends}, ${misc:Depends} Description: GObject introspection data for libqrtr-glib glib-based library to use and manage the QRTR (Qualcomm IPC Router) bus. . diff --git a/packaging/libqrtr-glib/debian/copyright b/packaging/libqrtr-glib/debian/copyright index daac14d..18e201e 100644 --- a/packaging/libqrtr-glib/debian/copyright +++ b/packaging/libqrtr-glib/debian/copyright @@ -9,12 +9,6 @@ Copyright: 2019-2021 Eric Caruso 2020-2021 Aleksander Morgado License: LGPL-2.1+ -Files: docs/reference/libqrtr-glib/libqrtr-glib-docs.xml -Copyright: 2021 Eric Caruso - 2021 Andrew Lassalle - 2021 Aleksander Morgado -License: GFDL-1.3+ - Files: debian/* Copyright: 2012 Marius B. Kotsbak 2021-2022 Arnaud Ferraris @@ -26,7 +20,7 @@ Copyright: 2021 Iñigo Martinez License: LGPL-2.1+ Files: meson_options.txt - docs/reference/meson.build + docs/reference/libqrtr-glib/meson.build src/libqrtr-glib/meson.build Copyright: 2021 Iñigo Martinez License: LGPL-2.1+ @@ -62,22 +56,10 @@ License: GPL-2+ details. . You should have received a copy of the GNU General Public - License along with this package; if not, write to the Free - Software Foundation, Inc., 51 Franklin St, Fifth Floor, - Boston, MA 02110-1301 USA + License along with this package. If not, see + . . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. -License: GFDL-1.3+ - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 or - any later version published by the Free Software Foundation; with no - Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license should be found with this document. If you did - not obtain the license, it can be found at - http://www.gnu.org/licenses/fdl.html. - . - On Debian systems, the complete text of the GNU Free Document License, - version 1.3 can be found in /usr/share/common-licenses/GFDL-1.3. diff --git a/packaging/libqrtr-glib/debian/gbp.conf b/packaging/libqrtr-glib/debian/gbp.conf index 48731a6..2b1fbcd 100644 --- a/packaging/libqrtr-glib/debian/gbp.conf +++ b/packaging/libqrtr-glib/debian/gbp.conf @@ -1,4 +1,11 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = debian/latest upstream-branch = upstream/latest +upstream-vcs-tag=%(version)s + +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit + +[dch] +multimaint-merge = True diff --git a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.doc-base b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.doc-base index 2a41a7e..aed92fb 100644 --- a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.doc-base +++ b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.doc-base @@ -5,5 +5,5 @@ Abstract: API documentation of libqrtr-glib. Section: Programming/C Format: HTML -Index: /usr/share/doc/libqrtr-glib-doc/html/index.html -Files: /usr/share/doc/libqrtr-glib-doc/html/*.html +Index: /usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0/index.html +Files: /usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0/*.html diff --git a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.install b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.install index 0172b3d..5ea80da 100644 --- a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.install +++ b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.install @@ -1 +1 @@ -usr/share/gtk-doc/html/libqrtr-glib +usr/share/doc/libqrtr-glib-1.0 usr/share/doc/libqrtr-glib-doc/ diff --git a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.links b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.links index 9205729..28a4555 100644 --- a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.links +++ b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.links @@ -1 +1,2 @@ -usr/share/gtk-doc/html/libqrtr-glib usr/share/doc/libqrtr-glib-doc/html +usr/share/doc/libqrtr-glib-doc/libqrtr-glib-1.0 usr/share/devhelp/books/libqrtr-glib-1.0 + diff --git a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.lintian-overrides b/packaging/libqrtr-glib/debian/libqrtr-glib-doc.lintian-overrides deleted file mode 100644 index 447dabf..0000000 --- a/packaging/libqrtr-glib/debian/libqrtr-glib-doc.lintian-overrides +++ /dev/null @@ -1,2 +0,0 @@ -# Documentation is generated using gtk-doc, and therefore is installed to `/usr/share/gtk-doc` -libqrtr-glib-doc: package-contains-documentation-outside-usr-share-doc usr/share/gtk-doc/html/libqrtr-glib/* diff --git a/packaging/libqrtr-glib/debian/rules b/packaging/libqrtr-glib/debian/rules index 4d21fb7..1c151b0 100755 --- a/packaging/libqrtr-glib/debian/rules +++ b/packaging/libqrtr-glib/debian/rules @@ -2,17 +2,10 @@ # -*- makefile -*- export DEB_BUILD_MAINT_OPTIONS = hardening=+all -DOPACKAGES=$(shell dh_listpackages) %: - dh $@ --with gir - -ifneq (,$(filter libqrtr-glib-doc,$(DOPACKAGES))) -CONFIGURE_FLAGS=-Dgtk_doc=true -else -CONFIGURE_FLAGS=-Dgtk_doc=false -endif + dh $@ override_dh_auto_configure: dh_auto_configure -- \ - $(CONFIGURE_FLAGS) + -Dgtk_doc=$(if $(filter %-doc,$(shell dh_listpackages)),true,false) diff --git a/packaging/libqrtr-glib/debian/source/options b/packaging/libqrtr-glib/debian/source/options deleted file mode 100644 index c9ff1fe..0000000 --- a/packaging/libqrtr-glib/debian/source/options +++ /dev/null @@ -1 +0,0 @@ -extend-diff-ignore="(^|/)(config\.sub|config\.guess)$" diff --git a/packaging/libqrtr-glib/debian/tests/build b/packaging/libqrtr-glib/debian/tests/build new file mode 100644 index 0000000..d453c18 --- /dev/null +++ b/packaging/libqrtr-glib/debian/tests/build @@ -0,0 +1,31 @@ +#!/bin/sh +# autopkgtest check: Build and run a program against libqrtr-glib, to verify that +# the headers and pkg-config file are installed correctly + +set -e + +if [ -n "${DEB_HOST_GNU_TYPE:-}" ]; then + CROSS_COMPILE="$DEB_HOST_GNU_TYPE-" +else + CROSS_COMPILE= +fi + +cat < libqrtr-glib_test.c +#include +#include + +int main(void) +{ + printf("The micro version is %d\n", QRTR_MICRO_VERSION); + return 0; +} +EOF + +# deliberately word-splitting pkg-config output: +# shellcheck disable=SC2046 +"${CROSS_COMPILE}gcc" -o libqrtr-glib_test libqrtr-glib_test.c \ +$("${CROSS_COMPILE}pkg-config" --cflags --libs qrtr-glib) +echo "build: OK" +[ -x libqrtr-glib_test ] +./libqrtr-glib_test +echo "run: OK" diff --git a/packaging/libqrtr-glib/debian/tests/control b/packaging/libqrtr-glib/debian/tests/control new file mode 100644 index 0000000..d836a36 --- /dev/null +++ b/packaging/libqrtr-glib/debian/tests/control @@ -0,0 +1,3 @@ +Tests: build +Depends: build-essential, pkg-config, libqrtr-glib-dev +Restrictions: allow-stderr superficial diff --git a/packaging/libqrtr-glib/debian/watch b/packaging/libqrtr-glib/debian/watch index 28bac55..3776ff3 100644 --- a/packaging/libqrtr-glib/debian/watch +++ b/packaging/libqrtr-glib/debian/watch @@ -1,3 +1,4 @@ -version=4 -https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@/-/tags?sort=updated_desc \ - .*/archive/\d\S+/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@ +Version: 5 + +Template: Gitlab +Dist: https://gitlab.freedesktop.org/mobile-broadband/@PACKAGE@ From 8fea23379025f665a10cddaf455e5f800839965c Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 00:11:18 -0500 Subject: [PATCH 03/14] packaging(ci): shared pin reader; revision-exact, direction-aware assertions --- packaging/README.md | 5 +- packaging/ci/contract.sh | 16 ++-- packaging/ci/daemon-smoke.sh | 14 ++-- packaging/ci/read-pin.sh | 105 +++++++++++++++++++++++++ packaging/ci/test-package-contract.sh | 106 ++++++++++++++++++++++---- 5 files changed, 219 insertions(+), 27 deletions(-) create mode 100755 packaging/ci/read-pin.sh diff --git a/packaging/README.md b/packaging/README.md index 85a33b6..1e8fcd9 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -82,11 +82,12 @@ ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, libqrtr-glib 1.4.0 (salsa |--------|------| | [`ci/tag-guard.sh`](ci/tag-guard.sh) | The release-tag contract: accepts only `vX.Y.Z`, fails closed on pre-release / build-metadata / missing-`v`. Sourced by `release.yml` (job 1) and by the version-injection + test scripts. | | [`ci/test-tag-guard.sh`](ci/test-tag-guard.sh) | Executable proof of the tag-guard negatives (`v1.0.0-rc.1`, `v1.0.0+build5`, `1.0.0`, …). Run in CI and locally. | +| [`ci/read-pin.sh`](ci/read-pin.sh) | The **shared pin reader**. `read-pin.sh ` prints any scalar from `upstream-pins.yaml` (e.g. `read-pin.sh modemmanager upstream_tag` → `1.24.2`); `read-pin.sh --base-version` prints the full Debian base `-` (e.g. `1.24.2-2`) from that source's `debian/changelog` top entry, **cross-checked** to equal the pin's `salsa_tag` suffix (mismatch fails closed). bash+awk only — its YAML reader is byte-identical to `verify-upstream-pins.sh`'s. Sourced by `daemon-smoke.sh`, `test-package-contract.sh`, and `contract.sh` so every version assertion tracks the pins (no hardcoded literals). | | [`ci/inject-deb-version.sh`](ci/inject-deb-version.sh) | Writes `-~ceralive` (or `~ceralive0.0.0~dev` for non-tag builds) into each source's `debian/changelog` top entry via `dch --force-bad-version`. Reads upstream versions from each source's changelog — never hardcoded here. | | [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins.sh) | Re-verifies every field of `upstream-pins.yaml` in an isolated `GNUPGHOME`: git-tag lineage (`git ls-remote`), `.dsc` GPG signature vs pinned signer, `.dsc` checksums vs manifest, the downloaded `.orig.tar` sha256, and — the 4th link — the `.debian.tar.xz` sha256 plus a canonical `debian/`-tree manifest compared against the pinned salsa tag (exec-bit + symlink-target aware). Exit 0 on success; non-zero with a NAMED failing field on any drift. | | [`ci/test-verify-upstream-pins.sh`](ci/test-verify-upstream-pins.sh) | Offline fail-closed proof: runs the four [`ci/fixtures/`](ci/fixtures) tampers (wrong-signer / altered-`.dsc` / altered-`.orig.tar` / altered-salsa-tree) and asserts each is rejected on the correct named field. Run standalone; the packaging-wave container lane can adopt it. | | [`ci/build-bookworm.sh`](ci/build-bookworm.sh) | Rebuilds all four sources in a `debian:bookworm` container in bootstrap order via a temporary local apt repo. `build-bookworm.sh ` — native amd64 or full-system-QEMU arm64, never cross-built. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → `~ceraliveX.Y.Z`; unset → `~ceralive0.0.0~dev`) into a **copy** of each changelog, runs real `dpkg-buildpackage`, and asserts the 9-package runtime closure from the `.changes` (drift ⇒ non-zero). Output to gitignored `build//`. | | [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (bookworm container) entry point. Lightweight, needs no built `.deb`: asserts the scaffold, the tag-guard contract, that `dch` version-injection runs on a **copy** (the committed changelogs stay pristine), and the real `dpkg --compare-versions` tilde ordering. The deb-consuming contract lives in the two scripts below. | -| [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure; clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | -| [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` == 1.24.0; the udev-rules + FCC-unlock dispatcher dirs exist; the GIR typelib (`gir1.2-modemmanager-1.0`) and Vala `.vapi` (`libmm-glib-dev`) are installed. amd64 by default. | +| [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure (revision-exact — every deb's base must equal its `read-pin.sh` `-`); clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set) with a **direction-aware** `--allow-downgrades` (computed per-package from real `dpkg --compare-versions` vs `madison` stock — post-bump every source sorts ABOVE stock, so the flag is dropped); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. All version literals are `read-pin.sh`-derived. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | +| [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` matches the **pinned** ModemManager upstream version (via `ci/read-pin.sh`, never hardcoded); the udev-rules + FCC-unlock dispatcher dirs exist; the GIR typelib (`gir1.2-modemmanager-1.0`) and Vala `.vapi` (`libmm-glib-dev`) are installed. amd64 by default. | | [`ci/generate-release-manifest.sh`](ci/generate-release-manifest.sh) | Emits the **per-release manifest** (`generate-release-manifest.sh ` → `dist/release-manifest.txt`) mapping the release tag to the 9 runtime deb versions **per arch** — the `arch package source version filename sha256` matrix Phase-B apt publication consumes. dpkg-free (filename parse + `sha256sum`), so it runs anywhere. | diff --git a/packaging/ci/contract.sh b/packaging/ci/contract.sh index b2c509d..441f235 100755 --- a/packaging/ci/contract.sh +++ b/packaging/ci/contract.sh @@ -33,6 +33,7 @@ require "README.md" require "BOOKWORM-ADAPTATIONS.md" require "ci/tag-guard.sh" require "ci/test-tag-guard.sh" +require "ci/read-pin.sh" require "ci/inject-deb-version.sh" require "ci/build-bookworm.sh" require "ci/test-package-contract.sh" @@ -43,6 +44,11 @@ require "ci/generate-release-manifest.sh" echo " running tag-guard contract..." bash "$HERE/test-tag-guard.sh" >/dev/null +# Reading the base here also runs read-pin.sh's changelog-top vs salsa_tag cross-check on every +# PR-lane run, so a `-1`-vs-`-2` revision drift fails closed before any ordering proof. +MM_BASE="$(bash "$HERE/read-pin.sh" modemmanager --base-version)" +echo " ok: pin reader resolves ModemManager base $MM_BASE (changelog top == salsa_tag suffix)" + # Version injection must run WITHOUT mutating the committed debian/changelog files. Now that # the recipes carry real changelogs (A5.1), `inject-deb-version.sh --dev` would dch-rewrite # the source-of-truth tree if run in place — so run it against a throwaway COPY and then @@ -78,12 +84,12 @@ fi # the encoded ~ceralive version depends on; a broken comparator would silently invert # release ordering. if command -v dpkg >/dev/null 2>&1; then - echo " running dpkg --compare-versions ordering proofs..." + echo " running dpkg --compare-versions ordering proofs (base $MM_BASE, pin-derived)..." ord_ok() { dpkg --compare-versions "$1" lt "$2" || { echo " FAIL: '$1' !lt '$2'"; fail=1; }; } - ord_ok "1.24.0-1~ceralive0.1.0" "1.24.0-1~ceralive0.2.0" - ord_ok "1.24.0-1~ceralive0.9.0" "1.24.0-1~ceralive0.10.0" - ord_ok "1.24.0-1~ceralive0.1.0" "1.24.0-1" - if dpkg --compare-versions "1.24.0-1~ceralive0.2.0" lt "1.24.0-1~ceralive0.1.0"; then + ord_ok "${MM_BASE}~ceralive0.1.0" "${MM_BASE}~ceralive0.2.0" + ord_ok "${MM_BASE}~ceralive0.9.0" "${MM_BASE}~ceralive0.10.0" + ord_ok "${MM_BASE}~ceralive0.1.0" "${MM_BASE}" + if dpkg --compare-versions "${MM_BASE}~ceralive0.2.0" lt "${MM_BASE}~ceralive0.1.0"; then echo " FAIL: comparator is always-true (0.2.0 lt 0.1.0)"; fail=1 fi echo " ok: tilde ordering holds" diff --git a/packaging/ci/daemon-smoke.sh b/packaging/ci/daemon-smoke.sh index 225550e..65082f8 100755 --- a/packaging/ci/daemon-smoke.sh +++ b/packaging/ci/daemon-smoke.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# daemon-smoke.sh — start the rebuilt ModemManager 1.24 on a real system bus +# daemon-smoke.sh — start the rebuilt ModemManager on a real system bus # and prove it is a working daemon, not just an installable file set. # # Inside a throwaway `debian:bookworm` container it installs system D-Bus + polkit + @@ -7,7 +7,8 @@ # then: # * starts a system dbus-daemon and the ModemManager daemon on it; # * `busctl introspect` the MM service at its root path -> the ObjectManager interface; -# * `mmcli --version` reports 1.24.0; +# * `mmcli --version` reports the PINNED ModemManager upstream version (read-pin.sh, never +# hardcoded — it tracks upstream-pins.yaml automatically across bumps); # * the udev-rules + FCC-unlock dispatcher directories exist at their install paths; # * the GIR typelib (gir1.2-modemmanager-1.0) and the Vala .vapi (libmm-glib-dev) are present. # @@ -130,12 +131,15 @@ INTRO="$(busctl --system introspect org.freedesktop.ModemManager1 /org/freedeskt echo "$INTRO" | sed 's/^/ /' if echo "$INTRO" | grep -q 'org.freedesktop.DBus.ObjectManager'; then ok "root path exposes org.freedesktop.DBus.ObjectManager"; else sed 's/^/ /' /tmp/introspect.err; bad "ObjectManager interface not found at root"; fi -# ---- ASSERTION 2: mmcli --version reports 1.24.0 ----------------------------------------- +# ---- ASSERTION 2: mmcli --version reports the PINNED upstream version -------------------- echo echo "==== mmcli --version ====" +# Derived from upstream-pins.yaml (never hardcoded): the mounted /pkg tree carries read-pin.sh. +MM_TAG="$(bash "$(dirname "${BASH_SOURCE[0]}")/read-pin.sh" modemmanager upstream_tag)" +MM_TAG_RE="${MM_TAG//./\\.}" MMCLI_V="$(mmcli --version 2>&1 | head -1)" -echo " $MMCLI_V" -echo "$MMCLI_V" | grep -q '1\.24\.0' && ok "mmcli reports 1.24.0" || bad "mmcli version is not 1.24.0" +echo " $MMCLI_V (expecting pinned upstream $MM_TAG)" +echo "$MMCLI_V" | grep -qE "(^|[^0-9.])${MM_TAG_RE}([^0-9]|\$)" && ok "mmcli reports $MM_TAG" || bad "mmcli version is not $MM_TAG" # ---- ASSERTION 3: udev-rules + FCC-unlock dispatcher directories at install paths -------- echo diff --git a/packaging/ci/read-pin.sh b/packaging/ci/read-pin.sh new file mode 100755 index 0000000..55bdb5d --- /dev/null +++ b/packaging/ci/read-pin.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# read-pin.sh — dependency-free reader for packaging/upstream-pins.yaml + the Debian base. +# +# TWO MODES +# read-pin.sh Print a scalar field of a source from upstream-pins.yaml +# (e.g. `read-pin.sh modemmanager upstream_tag` -> 1.24.2). +# read-pin.sh --base-version Print the FULL Debian base `-` (e.g. +# 1.24.2-2) taken from that source's debian/changelog TOP +# entry, cross-checked to equal the pin's salsa_tag suffix +# (`debian/1.24.2-2` -> `1.24.2-2`). Mismatch FAILS CLOSED. +# +# WHY A SHARED READER +# The packaging CI assertion scripts (daemon-smoke.sh, test-package-contract.sh, contract.sh) +# used to hardcode the pinned versions. When the pins move (they just did: MM 1.24.0-1 -> +# 1.24.2-2, libmbim 1.32.0 -> 1.34.0, libqmi 1.36.0 -> 1.38.0, libqrtr-glib 1.2.2 -> 1.4.0), +# those literals silently rot — worst of all a `-1` revision assertion against a real `-2` +# build. Routing every assertion through this one reader makes the pin the single source of +# truth and makes a stale-or-wrong-revision literal impossible. +# +# YAML PARSER PARITY +# yaml_scalar / yaml_sources below are kept BYTE-IDENTICAL to +# packaging/ci/verify-upstream-pins.sh's own readers. That verifier is frozen (todo 1.1), so +# the parser is duplicated here rather than factored out of it — it is the SAME parser, never +# a second, subtly-different one. Keep the two copies in lockstep if either is ever touched. +# +# DEPENDENCY-FREE +# bash + awk only (like the verifier). No dpkg / devscripts — so it runs unchanged on the +# Arch dev host and inside the bookworm CI container alike. +# +# EXIT +# 0 value printed on stdout. +# 1 unknown source, unknown/empty field, or a base-version cross-check mismatch (fail-closed, +# reason on stderr). +# 2 usage / unreadable manifest or changelog. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKG_ROOT="$(cd "$HERE/.." && pwd)" +MANIFEST="$PKG_ROOT/upstream-pins.yaml" + +[ $# -ge 2 ] || { echo "read-pin: usage: read-pin.sh " >&2; exit 2; } +SRC="$1" +FIELD="$2" +[ -r "$MANIFEST" ] || { echo "read-pin: cannot read manifest '$MANIFEST'" >&2; exit 2; } + +# ---- tiny dependency-free YAML readers (byte-identical to verify-upstream-pins.sh) --------- +# Scalar field of a source: sources.. -> unquoted value. +yaml_scalar() { + awk -v src="$1" -v key="$2" ' + $0 ~ "^ " src ":[ \t]*$" { inblk=1; next } + inblk && /^ [^ ]/ { inblk=0 } + inblk && /^[^ ]/ { inblk=0 } + inblk && $0 ~ "^ " key ":" { + v=$0; sub("^ " key ":[ \t]*", "", v); gsub(/^"|"$/, "", v); print v; exit + } + ' "$MANIFEST" +} +# The list of source names under `sources:`. +yaml_sources() { + awk ' + /^sources:[ \t]*$/ { ins=1; next } + ins && /^[^ ]/ { ins=0 } + ins && /^ [^ ]+:[ \t]*$/ { s=$0; sub(/^ /, "", s); sub(/:[ \t]*$/, "", s); print s } + ' "$MANIFEST" +} + +# The pinned source must exist (fail-closed on a typo'd / wrong source name). +src_known=0 +while IFS= read -r s; do + [ "$s" = "$SRC" ] && { src_known=1; break; } +done < <(yaml_sources) +[ "$src_known" -eq 1 ] || { echo "read-pin: unknown source '$SRC' (not in $MANIFEST)" >&2; exit 1; } + +# Map a pin source name to its checked-in debian/ recipe directory (only MM differs in case). +recipe_dir() { + case "$1" in + modemmanager) echo "ModemManager" ;; + *) echo "$1" ;; + esac +} + +if [ "$FIELD" = "--base-version" ]; then + # (1) Full Debian base from the changelog TOP entry: ` () ; ...`. + rdir="$(recipe_dir "$SRC")" + changelog="$PKG_ROOT/$rdir/debian/changelog" + [ -r "$changelog" ] || { echo "read-pin: cannot read changelog '$changelog'" >&2; exit 2; } + cl_ver="$(awk 'NR==1 { if (match($0, /\(([^)]+)\)/)) print substr($0, RSTART+1, RLENGTH-2); exit }' "$changelog")" + cl_base="${cl_ver%%~ceralive*}" # strip any injected ~ceralive suffix -> pure - + [ -n "$cl_base" ] || { echo "read-pin: could not parse a version from the top of $changelog" >&2; exit 1; } + # (2) Cross-check vs the pin's salsa_tag suffix: debian/1.24.2-2 -> 1.24.2-2. + salsa_tag="$(yaml_scalar "$SRC" salsa_tag)" + salsa_base="${salsa_tag#debian/}" + [ -n "$salsa_base" ] || { echo "read-pin: [$SRC] has no salsa_tag in $MANIFEST" >&2; exit 1; } + if [ "$cl_base" != "$salsa_base" ]; then + echo "read-pin: [$SRC] base-version mismatch — changelog top '$cl_base' != salsa_tag suffix '$salsa_base'" >&2 + exit 1 + fi + printf '%s\n' "$cl_base" + exit 0 +fi + +# Plain scalar field. +val="$(yaml_scalar "$SRC" "$FIELD")" +[ -n "$val" ] || { echo "read-pin: [$SRC] field '$FIELD' not found or empty in $MANIFEST" >&2; exit 1; } +printf '%s\n' "$val" diff --git a/packaging/ci/test-package-contract.sh b/packaging/ci/test-package-contract.sh index e0ac000..319eea1 100755 --- a/packaging/ci/test-package-contract.sh +++ b/packaging/ci/test-package-contract.sh @@ -136,6 +136,41 @@ resolve_debs() { } resolve_debs +# ---- pin-derived versions (single source of truth: upstream-pins.yaml + changelogs) ------- +# Every version literal the checks below assert against comes from read-pin.sh, so a pin bump +# (MM 1.24.0-1 -> 1.24.2-2, libmbim 1.32.0 -> 1.34.0, libqmi 1.36.0 -> 1.38.0, libqrtr-glib +# 1.2.2 -> 1.4.0) needs no edits here, and a wrong revision (`-1` vs the real `-2`) fails closed. +CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readpin() { bash "$CI_DIR/read-pin.sh" "$@"; } +MM_TAG="$(readpin modemmanager upstream_tag)" # upstream tag, matches mmcli --version +MM_BASE="$(readpin modemmanager --base-version)" # full Debian base -, revision-exact +MBIM_BASE="$(readpin libmbim --base-version)" +QMI_BASE="$(readpin libqmi --base-version)" +QRTR_BASE="$(readpin libqrtr-glib --base-version)" + +# Expected per-package upgrade direction vs bookworm stock, EMPIRICALLY resolved for this bump +# (real `dpkg --compare-versions` in a bookworm container, todo 1.3): every source now sorts +# ABOVE stock. libqrtr-glib flipped from BELOW -> ABOVE (1.2.2-1~ceralive was tilde-lower than +# stock 1.2.2-1; the new 1.4.0 outranks stock 1.2.2-1 outright), so the upgrade no longer needs +# --allow-downgrades. compute_direction_table asserts this holds against the real built debs. +declare -A EXPECT_DIR=( + [modemmanager]=above [libmm-glib0]=above + [libmbim-glib4]=above [libmbim-proxy]=above [libmbim-utils]=above + [libqmi-glib5]=above [libqmi-proxy]=above [libqmi-utils]=above + [libqrtr-glib0]=above +) +NEED_DOWNGRADE=0 + +# Each runtime package -> its source's revision-exact Debian base, so CHECK 1 asserts every +# built deb carries the correct - (not just any ~ceralive suffix); a stray `-1` +# on any of the nine fails closed. +declare -A PKG_BASE=( + [modemmanager]="$MM_BASE" [libmm-glib0]="$MM_BASE" + [libmbim-glib4]="$MBIM_BASE" [libmbim-proxy]="$MBIM_BASE" [libmbim-utils]="$MBIM_BASE" + [libqmi-glib5]="$QMI_BASE" [libqmi-proxy]="$QMI_BASE" [libqmi-utils]="$QMI_BASE" + [libqrtr-glib0]="$QRTR_BASE" +) + # ------------------------------------------------------------------------------------------ # CHECK 1 — metadata / architecture over the 9-package runtime closure. # ------------------------------------------------------------------------------------------ @@ -150,9 +185,11 @@ check_metadata() { printf ' %-20s %-32s %s\n' "$pkg" "$ver" "$arch" [ "$arch" = "$ARCH" ] || { echo " FAIL: arch $arch != $ARCH"; fail=1; } [ "${ver#*~ceralive}" != "$ver" ] || { echo " FAIL: version has no ~ceralive suffix"; fail=1; } + # Revision-exact: the base before ~ceralive must equal the pinned -. + [ "${ver%%~ceralive*}" = "${PKG_BASE[$pkg]}" ] || { echo " FAIL: $pkg base '${ver%%~ceralive*}' != pinned '${PKG_BASE[$pkg]}'"; fail=1; } done [ "$fail" -eq 0 ] || { echo " CHECK 1 FAIL"; return 1; } - echo " CHECK 1 PASS: 9 runtime packages, all Architecture=$ARCH, all ~ceralive-suffixed." + echo " CHECK 1 PASS: 9 runtime packages, all Architecture=$ARCH, all pin-exact -~ceralive." } # ------------------------------------------------------------------------------------------ @@ -182,8 +219,10 @@ check_coherence() { echo " ok: all 9 runtime debs share suffix '${suffix}'" # Negative fixture: a mismatched libqmi suffix MUST fail closed (QA-failure evidence). + # Real pin-derived bases; libqmi carries the odd ~ceralive0.2.0 suffix (MM + libmbim share + # 0.1.0), so the set is genuinely incoherent and must be rejected. echo " negative fixture (mismatched libqmi suffix expected to fail):" - local tampered=("1.24.0-1~ceralive0.1.0" "1.32.0-1~ceralive0.1.0" "1.36.0-1~ceralive0.2.0") + local tampered=("${MM_BASE}~ceralive0.1.0" "${MBIM_BASE}~ceralive0.1.0" "${QMI_BASE}~ceralive0.2.0") if assert_coherent "${tampered[@]}" >/dev/null 2>&1; then echo " CHECK 5 FAIL: coherence accepted a mismatched-libqmi set"; return 1 fi @@ -205,12 +244,12 @@ check_ordering() { if dpkg --compare-versions "$1" lt "$2"; then echo " FAIL: '$1' lt '$2' (expected NOT)"; fail=1 else echo " ok: '$1' not lt '$2'"; fi } - prove_lt "1.24.0-1~ceralive0.1.0" "1.24.0-1~ceralive0.2.0" - prove_lt "1.24.0-1~ceralive0.9.0" "1.24.0-1~ceralive0.10.0" - prove_lt "1.24.0-1~ceralive0.1.0" "1.24.0-1" - prove_lt "1.24.0-1~ceralive0.0.0~dev" "1.24.0-1~ceralive0.1.0" + prove_lt "${MM_BASE}~ceralive0.1.0" "${MM_BASE}~ceralive0.2.0" + prove_lt "${MM_BASE}~ceralive0.9.0" "${MM_BASE}~ceralive0.10.0" + prove_lt "${MM_BASE}~ceralive0.1.0" "${MM_BASE}" + prove_lt "${MM_BASE}~ceralive0.0.0~dev" "${MM_BASE}~ceralive0.1.0" # comparator must be real, not always-true: - prove_not_lt "1.24.0-1~ceralive0.2.0" "1.24.0-1~ceralive0.1.0" + prove_not_lt "${MM_BASE}~ceralive0.2.0" "${MM_BASE}~ceralive0.1.0" [ "$fail" -eq 0 ] || { echo " CHECK 6 FAIL"; return 1; } echo " CHECK 6 PASS: tilde ordering holds (pre-suffix < release; N.9 < N.10; dev < first)." } @@ -222,8 +261,10 @@ setup_local_repo() { cp /debs/*.deb "$REPO/" ( cd "$REPO" && dpkg-scanpackages -m . /dev/null > Packages 2>/dev/null ) echo "deb [trusted=yes] file:$REPO ./" > /etc/apt/sources.list.d/local-mm.list - # Pin the local (freshly built) stack above bookworm-main so the coherent ceralive set - # wins even where its upstream matches (libqrtr-glib 1.2.2 == bookworm's, tilde-lower). + # Pin the local (freshly built) stack at 1001 (> 1000) so the coherent ceralive set always + # wins regardless of direction. Every source now outranks bookworm-main on upstream version + # (incl. libqrtr-glib 1.4.0 > stock 1.2.2-1), so this is belt-and-suspenders, not a downgrade + # force — but the pin keeps the set coherent if a future stock point-release ever catches up. cat > /etc/apt/preferences.d/local-mm.pref <<'EOF' Package: * Pin: origin "" @@ -241,6 +282,34 @@ purge_stack() { } dpkg_ver() { dpkg-query -W -f='${Version}' "$1" 2>/dev/null || echo "(absent)"; } +# Real per-package upgrade direction: each built ceralive deb vs bookworm-main stock, via actual +# `dpkg --compare-versions`. Asserts every package lands on its EXPECT_DIR side and sets +# NEED_DOWNGRADE=1 iff any source sorts below stock (so the upgrade passes --allow-downgrades +# only where genuinely required). Call with the local repo DISABLED so madison yields stock. +compute_direction_table() { + echo " ---- upgrade direction table (built ceralive deb vs bookworm stock, real dpkg) ----" + printf ' %-16s %-30s %-12s %-7s %s\n' PACKAGE CERALIVE STOCK DIR EXPECT + local pkg built stock dir exp fail=0 + NEED_DOWNGRADE=0 + for pkg in "${RUNTIME_PKGS[@]}"; do + built="$(dpkg-deb -f "${DEB_OF[$pkg]}" Version)" + stock="$(apt-cache madison "$pkg" 2>/dev/null | awk -F'|' 'NR==1{gsub(/^[ \t]+|[ \t]+$/,"",$2); print $2; exit}')" + if [ -z "$stock" ]; then dir="no-stock" + elif dpkg --compare-versions "$built" gt "$stock"; then dir="above" + elif dpkg --compare-versions "$built" lt "$stock"; then dir="below"; NEED_DOWNGRADE=1 + else dir="equal"; fi + exp="${EXPECT_DIR[$pkg]:-above}" + printf ' %-16s %-30s %-12s %-7s %s\n' "$pkg" "$built" "${stock:-}" "$dir" "$exp" + [ "$dir" = "$exp" ] || { echo " FAIL: $pkg sorts '$dir' vs stock, expected '$exp'"; fail=1; } + done + [ "$fail" -eq 0 ] || { echo " DIRECTION TABLE FAIL: a package is on the wrong side of stock"; return 1; } + if [ "$NEED_DOWNGRADE" -eq 1 ]; then + echo " => a source sorts BELOW stock; the upgrade requires --allow-downgrades." + else + echo " => every source sorts ABOVE stock; the upgrade needs NO --allow-downgrades." + fi +} + # ------------------------------------------------------------------------------------------ # CHECK 2 — clean-bookworm dependency-closure install via `apt-get install ./*.deb`. # ------------------------------------------------------------------------------------------ @@ -277,20 +346,27 @@ check_upgrade() { echo " stock modemmanager installed: $before" case "$before" in ${STOCK_MM_UPSTREAM}*) echo " ok: stock is ${STOCK_MM_UPSTREAM}-series" ;; *) echo " note: bookworm stock modemmanager is $before" ;; esac + # Compute the real upgrade direction now, while the local repo is still disabled so madison + # reports the true bookworm-main stock version for each package. + compute_direction_table || { echo " CHECK 3 FAIL: direction table"; return 1; } + echo " enabling local ceralive repo and upgrading the coherent set..." setup_local_repo - # --allow-downgrades: libqrtr-glib 1.2.2-1~ceralive is tilde-LOWER than bookworm's 1.2.2-1 - # (same upstream), so landing the FULL coherent ceralive set is a downgrade for that one - # package even though modemmanager itself genuinely upgrades 1.20.4 -> 1.24.0. - apt-get install -y -qq --allow-downgrades "${RUNTIME_PKGS[@]}" >/tmp/upgrade.log 2>&1 || { sed 's/^/ /' /tmp/upgrade.log; echo " CHECK 3 FAIL: upgrade"; return 1; } + # --allow-downgrades is added ONLY if the direction table found a source below stock. For this + # bump every source outranks stock (libqrtr-glib 1.4.0 > 1.2.2-1), so the flag is omitted and + # this is a genuine, no-downgrade upgrade — a stricter assertion than the old blanket flag. + local dgflag=() + [ "$NEED_DOWNGRADE" -eq 1 ] && dgflag=(--allow-downgrades) + apt-get install -y -qq "${dgflag[@]}" "${RUNTIME_PKGS[@]}" >/tmp/upgrade.log 2>&1 || { sed 's/^/ /' /tmp/upgrade.log; echo " CHECK 3 FAIL: upgrade (flags: ${dgflag[*]:-none})"; return 1; } local after; after="$(dpkg_ver modemmanager)" echo " modemmanager after upgrade: $after" dpkg --compare-versions "$before" lt "$after" || { echo " CHECK 3 FAIL: modemmanager did not move UP ($before !< $after)"; return 1; } - case "$after" in 1.24.0*~ceralive*) echo " ok: modemmanager upgraded to 1.24.0 ceralive" ;; *) echo " CHECK 3 FAIL: unexpected upgraded version $after"; return 1 ;; esac + # Revision-EXACT: the full - base (e.g. 1.24.2-2) must match; a `-1` build fails. + case "$after" in ${MM_BASE}~ceralive*) echo " ok: modemmanager upgraded to ${MM_BASE} ceralive" ;; *) echo " CHECK 3 FAIL: expected ${MM_BASE}~ceralive*, got $after"; return 1 ;; esac local pkg fail=0 for pkg in "${RUNTIME_PKGS[@]}"; do case "$(dpkg_ver "$pkg")" in *~ceralive*) : ;; *) echo " FAIL: $pkg not on ceralive after upgrade"; fail=1 ;; esac; done [ "$fail" -eq 0 ] || { echo " CHECK 3 FAIL"; return 1; } - echo " CHECK 3 PASS: apt upgraded modemmanager 1.20.4 -> 1.24.0 and landed the full coherent set." + echo " CHECK 3 PASS: apt upgraded modemmanager ${STOCK_MM_UPSTREAM} -> ${MM_TAG} and landed the full coherent set." purge_stack disable_local_repo } From aa755ecda36f5ed23bc9658da87bb4480ee76c4a Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 00:57:50 -0500 Subject: [PATCH 04/14] packaging(ci): enforce exact per-source package sets Add ci/check-package-sets.sh: for each *.changes, assert its binary package set EQUALS (not >=/count) the [ all-artifact] set in expected-packages.txt, so any add/remove/rename fails closed naming the package. Wire it into build-bookworm.sh after the runtime-closure check and run it standalone per-arch. Finalize expected-packages.txt all-artifact sets against the real amd64 and arm64 *.changes (PROVISIONAL -> FINALIZED). The dbgsym enumeration inherited from the debian/ refresh proved exact: a -dbgsym is emitted for exactly the 9 ELF-shipping packages and nothing else; no other delta on either arch. build-bookworm.sh: install the freshly-built gir1.2-*-1.0 typelib packages into the build environment before each dependent source. Bookworm's GI-1.74 dh_girepository resolves cross-namespace typelib deps (Qmi-1.0 imports Qrtr-1.0; ModemManager-1.0 imports Qmi/Mbim/Qrtr) via the installed dependency .typelib, and the rebuilt -dev packages carry an empty ${gir:Depends} under GI-1.74, so libqmi otherwise died with "Could not find Qrtr-1.0.typelib dependency". This is build-orchestration only (mirrors an archive build where every gir1.2-* is co-installable) and changes no produced package's contents or the emitted set. Document both in packaging/README.md. --- packaging/README.md | 3 +- packaging/ci/build-bookworm.sh | 24 +++++++- packaging/ci/check-package-sets.sh | 91 ++++++++++++++++++++++++++++++ packaging/ci/expected-packages.txt | 24 ++++---- 4 files changed, 129 insertions(+), 13 deletions(-) create mode 100755 packaging/ci/check-package-sets.sh diff --git a/packaging/README.md b/packaging/README.md index 1e8fcd9..01878a5 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -86,7 +86,8 @@ ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, libqrtr-glib 1.4.0 (salsa | [`ci/inject-deb-version.sh`](ci/inject-deb-version.sh) | Writes `-~ceralive` (or `~ceralive0.0.0~dev` for non-tag builds) into each source's `debian/changelog` top entry via `dch --force-bad-version`. Reads upstream versions from each source's changelog — never hardcoded here. | | [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins.sh) | Re-verifies every field of `upstream-pins.yaml` in an isolated `GNUPGHOME`: git-tag lineage (`git ls-remote`), `.dsc` GPG signature vs pinned signer, `.dsc` checksums vs manifest, the downloaded `.orig.tar` sha256, and — the 4th link — the `.debian.tar.xz` sha256 plus a canonical `debian/`-tree manifest compared against the pinned salsa tag (exec-bit + symlink-target aware). Exit 0 on success; non-zero with a NAMED failing field on any drift. | | [`ci/test-verify-upstream-pins.sh`](ci/test-verify-upstream-pins.sh) | Offline fail-closed proof: runs the four [`ci/fixtures/`](ci/fixtures) tampers (wrong-signer / altered-`.dsc` / altered-`.orig.tar` / altered-salsa-tree) and asserts each is rejected on the correct named field. Run standalone; the packaging-wave container lane can adopt it. | -| [`ci/build-bookworm.sh`](ci/build-bookworm.sh) | Rebuilds all four sources in a `debian:bookworm` container in bootstrap order via a temporary local apt repo. `build-bookworm.sh ` — native amd64 or full-system-QEMU arm64, never cross-built. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → `~ceraliveX.Y.Z`; unset → `~ceralive0.0.0~dev`) into a **copy** of each changelog, runs real `dpkg-buildpackage`, and asserts the 9-package runtime closure from the `.changes` (drift ⇒ non-zero). Output to gitignored `build//`. | +| [`ci/build-bookworm.sh`](ci/build-bookworm.sh) | Rebuilds all four sources in a `debian:bookworm` container in bootstrap order via a temporary local apt repo. `build-bookworm.sh ` — native amd64 or full-system-QEMU arm64, never cross-built. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → `~ceraliveX.Y.Z`; unset → `~ceralive0.0.0~dev`) into a **copy** of each changelog, installs the freshly-built `gir1.2-*-1.0` typelibs into the build env before each dependent source (so bookworm's GI-1.74 `dh_girepository` can resolve cross-namespace typelib deps — Qmi→Qrtr, MM→Qmi/Mbim/Qrtr), runs real `dpkg-buildpackage`, and asserts BOTH the 9-package runtime closure AND per-source package-set **equality** (via `ci/check-package-sets.sh`) from the `.changes` (drift ⇒ non-zero). Output to gitignored `build//`. | +| [`ci/check-package-sets.sh`](ci/check-package-sets.sh) | Exact per-source package-set **equality** enforcement. `check-package-sets.sh [expected-packages.txt]` asserts every `*.changes` binary set EQUALS its `[ all-artifact]` set in [`ci/expected-packages.txt`](ci/expected-packages.txt) (the finalized two-set model: declared arch-dependent stanzas + enumerated `-dbgsym`). Equality — not `≥`/count — so an add/remove/rename fails closed naming the offending package. Invoked by `build-bookworm.sh` in-container after the closure check, and standalone per-arch. | | [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (bookworm container) entry point. Lightweight, needs no built `.deb`: asserts the scaffold, the tag-guard contract, that `dch` version-injection runs on a **copy** (the committed changelogs stay pristine), and the real `dpkg --compare-versions` tilde ordering. The deb-consuming contract lives in the two scripts below. | | [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure (revision-exact — every deb's base must equal its `read-pin.sh` `-`); clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set) with a **direction-aware** `--allow-downgrades` (computed per-package from real `dpkg --compare-versions` vs `madison` stock — post-bump every source sorts ABOVE stock, so the flag is dropped); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. All version literals are `read-pin.sh`-derived. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | | [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` matches the **pinned** ModemManager upstream version (via `ci/read-pin.sh`, never hardcoded); the udev-rules + FCC-unlock dispatcher dirs exist; the GIR typelib (`gir1.2-modemmanager-1.0`) and Vala `.vapi` (`libmm-glib-dev`) are installed. amd64 by default. | diff --git a/packaging/ci/build-bookworm.sh b/packaging/ci/build-bookworm.sh index fee7cc0..73abde9 100755 --- a/packaging/ci/build-bookworm.sh +++ b/packaging/ci/build-bookworm.sh @@ -194,6 +194,21 @@ build_one() { log "apt-get build-dep (resolves against local repo for stack deps)" apt-get build-dep -y --no-install-recommends "$tree" >/dev/null + # dh_girepository (bookworm GI 1.74) resolves cross-namespace typelib deps (Qmi-1.0 imports + # Qrtr-1.0; ModemManager-1.0 imports Qmi/Mbim/Qrtr) via the *installed* dependency .typelib, + # which ships only in the stack's own gir1.2-*-1.0 packages. On bookworm the rebuilt -dev + # build-deps do NOT pull them (GI 1.74 regenerates an empty ${gir:Depends} for -dev), so + # libqmi dies with "Could not find Qrtr-1.0.typelib dependency". Install every gir typelib + # already in the local repo first — mirrors an archive build (all gir1.2-* co-installable); + # affects only the build env, never a produced package's contents or the emitted set. + local gir_names + gir_names="$(find "$REPO" -maxdepth 1 -name 'gir1.2-*.deb' -printf '%f\n' 2>/dev/null \ + | sed 's/_.*//' | sort -u | tr '\n' ' ')" + if [ -n "${gir_names// /}" ]; then + log "install freshly-built gir typelibs so dh_girepository resolves them: $gir_names" + apt-get install -y --no-install-recommends $gir_names >/dev/null + fi + # Real binary build, arch-only (-B): all 9 runtime pkgs are arch-specific; -B skips the # arch:all -doc pkgs and the -indep DEP-8 patch target. log "dpkg-buildpackage -B (DEB_BUILD_OPTIONS='$DEB_BUILD_OPTIONS')" @@ -249,5 +264,12 @@ else exit 3 fi +# ---- exact per-source package-set EQUALITY (finalized two-set model) ---------------------- +# Stronger than the runtime closure above: asserts EVERY source's *.changes binary set equals +# its frozen [ all-artifact] set in expected-packages.txt, exactly (add/remove/rename +# all fail). check-package-sets.sh fails closed and names the discrepancy. +step "package-set equality verification (per source+arch, from *.changes)" +bash "$PKGW/ci/check-package-sets.sh" /out "$PKGW/ci/expected-packages.txt" + echo -echo "PASS [$ARCH]: 4 sources built in bootstrap order; runtime closure == the 9." +echo "PASS [$ARCH]: 4 sources built in bootstrap order; runtime closure == the 9; per-source .changes sets EQUAL the finalized all-artifact expectation." diff --git a/packaging/ci/check-package-sets.sh b/packaging/ci/check-package-sets.sh new file mode 100755 index 0000000..3cbbdd9 --- /dev/null +++ b/packaging/ci/check-package-sets.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# check-package-sets.sh [expected-packages.txt] — exact per-source package-set +# EQUALITY enforcement for the ModemManager stack rebuild. +# +# WHAT IT DOES +# For every *.changes in , reads its `Source:` and `Binary:` fields and asserts +# the produced binary package set EQUALS, with no additions / removals / renames, the +# `[ all-artifact]` set frozen in expected-packages.txt (the two-set model finalized +# in todo 1.4: declared arch-dependent stanzas + enumerated -dbgsym outputs). +# +# WHY EQUALITY, NOT A COUNT +# A `>=` or count-based check passes an add+remove that nets to the same size (a silent +# rename, or a dropped runtime package masked by a new -dbgsym). Set EQUALITY catches it and +# names the exact offending package. +# +# USED BY +# build-bookworm.sh (in-container, after the runtime-closure check — fail-closed on drift) +# and standalone on the host against build// for per-arch equality evidence + the +# set-equality negative drill. +# +# EXIT +# 0 every source's set == its finalized all-artifact set. +# 3 a set differs (names the source + the exact missing/unexpected packages). +# 2 usage / unreadable input / a source with no [all-artifact] block. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CHANGES_DIR="${1:-}" +EXPECTED="${2:-$HERE/expected-packages.txt}" + +[ -n "$CHANGES_DIR" ] && [ -d "$CHANGES_DIR" ] || { + echo "usage: check-package-sets.sh [expected-packages.txt]" >&2; exit 2; } +[ -r "$EXPECTED" ] || { echo "check-package-sets: cannot read '$EXPECTED'" >&2; exit 2; } + +# Source name from a .changes `Source:` field (drops any trailing "(version)"). +changes_source() { awk '/^Source:/ { print $2; exit }' "$1"; } + +# Binary package names from a .changes `Binary:` field (RFC822, fold-safe), sorted-unique. +changes_binaries() { + awk ' + /^[A-Za-z][A-Za-z0-9-]*:/ { inb=0 } + /^Binary:/ { inb=1; l=$0; sub(/^Binary:[ \t]*/, "", l); print l; next } + inb && /^[ \t]/ { l=$0; sub(/^[ \t]+/, "", l); print l } + ' "$1" | tr ' ' '\n' | sed '/^$/d' | sort -u +} + +# The `[ all-artifact]` block of expected-packages.txt (inline/# comments stripped). +expected_set() { + awk -v want="[$1 all-artifact]" ' + /^\[/ { h=$0; sub(/[ \t]*#.*$/, "", h); insec=(h==want)?1:0; next } + insec { l=$0; sub(/#.*$/, "", l); gsub(/[ \t]+/, "", l); if (l!="") print l } + ' "$EXPECTED" | sort -u +} + +shopt -s nullglob +changes_files=("$CHANGES_DIR"/*.changes) +[ "${#changes_files[@]}" -gt 0 ] || { echo "check-package-sets: no *.changes in $CHANGES_DIR" >&2; exit 2; } + +rc=0 +for ch in "${changes_files[@]}"; do + src="$(changes_source "$ch")" + got="$(changes_binaries "$ch")" + want="$(expected_set "$src")" + echo "== ${src} (from $(basename "$ch")) ==" + if [ -z "$want" ]; then + echo "FAIL [${src}] no [${src} all-artifact] block in $(basename "$EXPECTED")" >&2 + rc=3; continue + fi + echo " expected all-artifact set ($(printf '%s\n' "$want" | grep -c .)):" + printf '%s\n' "$want" | sed 's/^/ /' + echo " produced binary set ($(printf '%s\n' "$got" | grep -c .)):" + printf '%s\n' "$got" | sed 's/^/ /' + if [ "$want" = "$got" ]; then + echo " EQUAL: OK" + else + echo "FAIL [${src}] package-set inequality (produced != finalized all-artifact):" >&2 + comm -23 <(printf '%s\n' "$want") <(printf '%s\n' "$got") \ + | sed 's/^/ MISSING (expected, not built): /' >&2 || true + comm -13 <(printf '%s\n' "$want") <(printf '%s\n' "$got") \ + | sed 's/^/ UNEXPECTED (built, not expected): /' >&2 || true + rc=3 + fi +done + +echo +if [ "$rc" -eq 0 ]; then + echo "PACKAGE-SET EQUALITY OK: every source .changes set == its finalized all-artifact set." +else + echo "STOP: package-set equality failed (see MISSING/UNEXPECTED above)." >&2 +fi +exit "$rc" diff --git a/packaging/ci/expected-packages.txt b/packaging/ci/expected-packages.txt index abec20d..f95b99f 100644 --- a/packaging/ci/expected-packages.txt +++ b/packaging/ci/expected-packages.txt @@ -18,13 +18,15 @@ # build time), so they are enumerated EXPLICITLY below. This is the # reference for 1.4's `.changes` equality (both arches) and 2.1's manifest. # -# PROVISIONAL NOTICE (all-artifact only) -# The [all-artifact] sets below are PROVISIONAL — a best-effort enumeration derived from -# which declared packages ship ELF (shared libs, the daemon, CLI utils, proxies) vs which -# ship no ELF (-dev = headers/.so-symlink/pkgconfig/vapi/gir; gir1.2-* = .typelib, not a -# stripped ELF object). They are FINALIZED in todo 1.4 against the first clean amd64 -# `.changes`, cross-checked to equal declared+dbgsym. Any other delta at that point is a -# HARD STOP. The [declared] sets are NOT provisional — they are frozen from debian/control. +# FINALIZED (todo 1.4) +# The [all-artifact] sets below were FINALIZED against the real *.changes from the first +# clean bookworm rebuild (todo 1.4, amd64 then arm64) and cross-checked to equal +# declared+dbgsym exactly — no other delta. The dbgsym enumeration proved correct as +# derived: a -dbgsym is emitted for exactly the 9 ELF-shipping packages (the runtime +# closure) and for nothing else (-dev = headers/.so-symlink/pkgconfig/vapi/gir; gir1.2-* = +# .typelib, neither ships a stripped ELF). These sets are enforced for EQUALITY per +# source+arch by ci/check-package-sets.sh (add/remove/rename fails closed). The [declared] +# sets remain frozen from debian/control. # # DERIVATION (which declared packages get a -dbgsym) # ELF-shipping (=> gets -dbgsym): the 9 runtime packages — modemmanager (daemon+plugins), @@ -42,7 +44,7 @@ libmm-glib0 libmm-glib-dev gir1.2-modemmanager-1.0 -[modemmanager all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +[modemmanager all-artifact] # FINALIZED (todo 1.4, vs the real amd64 + arm64 .changes) modemmanager modemmanager-dev libmm-glib0 @@ -58,7 +60,7 @@ libmbim-utils libmbim-proxy gir1.2-mbim-1.0 -[libmbim all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +[libmbim all-artifact] # FINALIZED (todo 1.4, vs the real amd64 + arm64 .changes) libmbim-glib4 libmbim-glib-dev libmbim-utils @@ -75,7 +77,7 @@ libqmi-utils libqmi-proxy gir1.2-qmi-1.0 -[libqmi all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +[libqmi all-artifact] # FINALIZED (todo 1.4, vs the real amd64 + arm64 .changes) libqmi-glib-dev libqmi-glib5 libqmi-utils @@ -90,7 +92,7 @@ libqrtr-glib0 libqrtr-glib-dev gir1.2-qrtr-1.0 -[libqrtr-glib all-artifact] # PROVISIONAL (finalized in 1.4 vs first clean amd64 .changes) +[libqrtr-glib all-artifact] # FINALIZED (todo 1.4, vs the real amd64 + arm64 .changes) libqrtr-glib0 libqrtr-glib-dev gir1.2-qrtr-1.0 From 6432407121dfbe91c8dcfc99b8f4108353065569 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 01:33:59 -0500 Subject: [PATCH 05/14] packaging(ci): functional GI validation in daemon smoke --- packaging/README.md | 2 +- packaging/ci/daemon-smoke.sh | 49 +++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packaging/README.md b/packaging/README.md index 01878a5..23c3479 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -90,5 +90,5 @@ ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, libqrtr-glib 1.4.0 (salsa | [`ci/check-package-sets.sh`](ci/check-package-sets.sh) | Exact per-source package-set **equality** enforcement. `check-package-sets.sh [expected-packages.txt]` asserts every `*.changes` binary set EQUALS its `[ all-artifact]` set in [`ci/expected-packages.txt`](ci/expected-packages.txt) (the finalized two-set model: declared arch-dependent stanzas + enumerated `-dbgsym`). Equality — not `≥`/count — so an add/remove/rename fails closed naming the offending package. Invoked by `build-bookworm.sh` in-container after the closure check, and standalone per-arch. | | [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (bookworm container) entry point. Lightweight, needs no built `.deb`: asserts the scaffold, the tag-guard contract, that `dch` version-injection runs on a **copy** (the committed changelogs stay pristine), and the real `dpkg --compare-versions` tilde ordering. The deb-consuming contract lives in the two scripts below. | | [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure (revision-exact — every deb's base must equal its `read-pin.sh` `-`); clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set) with a **direction-aware** `--allow-downgrades` (computed per-package from real `dpkg --compare-versions` vs `madison` stock — post-bump every source sorts ABOVE stock, so the flag is dropped); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. All version literals are `read-pin.sh`-derived. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | -| [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` matches the **pinned** ModemManager upstream version (via `ci/read-pin.sh`, never hardcoded); the udev-rules + FCC-unlock dispatcher dirs exist; the GIR typelib (`gir1.2-modemmanager-1.0`) and Vala `.vapi` (`libmm-glib-dev`) are installed. amd64 by default. | +| [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` matches the **pinned** ModemManager upstream version (via `ci/read-pin.sh`, never hardcoded); the udev-rules + FCC-unlock dispatcher dirs exist; and — **functional GI validation**, not presence-only (it installs `python3-gi valac build-essential pkg-config`) — the `gir1.2-modemmanager-1.0` typelib **loads** through PyGObject (`gi.require_version('ModemManager','1.0')` + a real `ModemManager.ModemCapability.LTE` enum read) and the `libmm-glib` `.vapi` **compiles+links** via `valac -C` → `cc $(pkg-config --cflags --libs mm-glib)` against a Vala program that genuinely calls a libmm-glib symbol (a broken/absent GI-1.74 adaptation fails closed here). amd64 by default. | | [`ci/generate-release-manifest.sh`](ci/generate-release-manifest.sh) | Emits the **per-release manifest** (`generate-release-manifest.sh ` → `dist/release-manifest.txt`) mapping the release tag to the 9 runtime deb versions **per arch** — the `arch package source version filename sha256` matrix Phase-B apt publication consumes. dpkg-free (filename parse + `sha256sum`), so it runs anywhere. | diff --git a/packaging/ci/daemon-smoke.sh b/packaging/ci/daemon-smoke.sh index 65082f8..d0e313d 100755 --- a/packaging/ci/daemon-smoke.sh +++ b/packaging/ci/daemon-smoke.sh @@ -148,13 +148,50 @@ ls /usr/lib/udev/rules.d/77-mm-*.rules >/dev/null 2>&1 && ok "udev rules present [ -d /etc/ModemManager/fcc-unlock.d ] && ok "FCC-unlock dispatcher dir (/etc/ModemManager/fcc-unlock.d)" || bad "FCC-unlock dispatcher dir missing" [ -d /usr/share/ModemManager/fcc-unlock.available.d ] && ok "FCC-unlock available dir (/usr/share/ModemManager/fcc-unlock.available.d)" || bad "FCC-unlock available dir missing" -# ---- ASSERTION 4: GIR typelib + Vala .vapi in the installed file set ---------------------- +# ---- ASSERTION 4: GIR typelib + Vala .vapi are FUNCTIONAL, not merely present ------------- +# A presence-only stat of the .typelib / .gir / .vapi would still pass if the GI-1.74 bookworm +# adaptation emitted a typelib PyGObject cannot load or a .vapi valac cannot compile against. +# Exercise both for real instead — either failure invalidates the GI-1.74 adaptation. echo -echo "==== GIR / Vala artifacts ====" -TYPELIB="/usr/lib/${MA}/girepository-1.0/ModemManager-1.0.typelib" -[ -f "$TYPELIB" ] && ok "GIR typelib present ($TYPELIB)" || bad "GIR typelib missing ($TYPELIB)" -[ -f /usr/share/gir-1.0/ModemManager-1.0.gir ] && ok "GIR xml present (/usr/share/gir-1.0/ModemManager-1.0.gir)" || bad "GIR xml missing" -[ -f /usr/share/vala/vapi/libmm-glib.vapi ] && ok "Vala .vapi present (/usr/share/vala/vapi/libmm-glib.vapi)" || bad "Vala .vapi missing" +echo "==== GIR / Vala FUNCTIONAL checks (typelib import + Vala compile-link) ====" +echo "-- typelib multiarch dir: /usr/lib/${MA}/girepository-1.0 --" +echo "-- installing python3-gi + valac + build-essential + pkg-config --" +apt-get install -y -qq python3-gi valac build-essential pkg-config >/tmp/gi-tooling.log 2>&1 \ + || { sed 's/^/ /' /tmp/gi-tooling.log; bad "GI tooling install failed"; } + +# (i) Typelib functional import: PyGObject loads ModemManager-1.0.typelib and resolves a REAL +# enum member. The GI Python form is the enum member ModemManager.ModemCapability.LTE, NOT a +# flat MODEM_CAPABILITY_LTE constant (that flat form does not exist under GI's Python bindings). +if GI_OUT="$(python3 -c 'import gi; gi.require_version("ModemManager", "1.0"); from gi.repository import ModemManager; print(ModemManager.ModemCapability.LTE)' 2>/tmp/gi-py.err)"; then + ok "typelib import via PyGObject works: ModemManager.ModemCapability.LTE = ${GI_OUT}" +else + sed 's/^/ /' /tmp/gi-py.err; bad "typelib functional import failed (PyGObject could not load ModemManager-1.0.typelib)" +fi + +# (ii) Vala compile+link that GENUINELY uses the library: it references the enum member +# MM.ModemCapability.LTE and calls the real libmm-glib function build_string_from_mask() — +# a genuine undefined symbol resolved from libmm-glib.so at link time, so a no-symbol +# program cannot false-pass. valac -C emits C from the .vapi; cc compiles+links it with the +# mm-glib pkg-config flags. (Running the binary is a bonus — it needs no D-Bus here.) +VALA_DIR="$(mktemp -d)" +cat > "$VALA_DIR/mini.vala" <<'EOF' +void main () { + var cap = MM.ModemCapability.LTE; + string s = cap.build_string_from_mask (); + stdout.printf ("MM.ModemCapability.LTE=%d mask=%s\n", (int) cap, s); +} +EOF +if ( cd "$VALA_DIR" && valac -C --pkg libmm-glib mini.vala ) >/tmp/valac.log 2>&1 && [ -f "$VALA_DIR/mini.c" ]; then + read -ra MM_PC < <(pkg-config --cflags --libs mm-glib) || true + if ( cd "$VALA_DIR" && cc mini.c "${MM_PC[@]}" -o mini ) >/tmp/cc.log 2>&1; then + ok "Vala compile+link against libmm-glib succeeded (valac -C + cc \$(pkg-config mm-glib)); ran: $("$VALA_DIR/mini" 2>/dev/null || echo '')" + else + sed 's/^/ /' /tmp/cc.log; bad "Vala C compile+link against libmm-glib failed" + fi +else + sed 's/^/ /' /tmp/valac.log; bad "valac could not compile the Vala program against libmm-glib (.vapi absent or GI adaptation broken)" +fi +rm -rf "$VALA_DIR" # ---- teardown ---------------------------------------------------------------------------- kill "$MM_PID" 2>/dev/null || true From f51118cc46698f0620fedb7e8aecc66181537d72 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 01:39:53 -0500 Subject: [PATCH 06/14] chore(deps): biome 2.5.4; dependabot policy: no TS major bumps --- .github/dependabot.yml | 4 ++++ bun.lock | 20 ++++++++++---------- package.json | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 44a99ac..f4e5a5a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,3 +17,7 @@ updates: groups: npm: patterns: ["*"] + # TypeScript 7 is explicitly out of scope; never propose a semver-major TS bump. + ignore: + - dependency-name: "typescript" + update-types: ["version-update:semver-major"] diff --git a/bun.lock b/bun.lock index 74bd565..20dfb5b 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "modem-stack", "devDependencies": { - "@biomejs/biome": "2.5.3", + "@biomejs/biome": "2.5.4", "@ceralive/biome-config": "2026.6.2", "@types/bun": "1.3.14", "typescript": "^5", @@ -32,23 +32,23 @@ }, }, "packages": { - "@biomejs/biome": ["@biomejs/biome@2.5.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.3", "@biomejs/cli-darwin-x64": "2.5.3", "@biomejs/cli-linux-arm64": "2.5.3", "@biomejs/cli-linux-arm64-musl": "2.5.3", "@biomejs/cli-linux-x64": "2.5.3", "@biomejs/cli-linux-x64-musl": "2.5.3", "@biomejs/cli-win32-arm64": "2.5.3", "@biomejs/cli-win32-x64": "2.5.3" }, "bin": { "biome": "bin/biome" } }, "sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A=="], + "@biomejs/biome": ["@biomejs/biome@2.5.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.4", "@biomejs/cli-darwin-x64": "2.5.4", "@biomejs/cli-linux-arm64": "2.5.4", "@biomejs/cli-linux-arm64-musl": "2.5.4", "@biomejs/cli-linux-x64": "2.5.4", "@biomejs/cli-linux-x64-musl": "2.5.4", "@biomejs/cli-win32-arm64": "2.5.4", "@biomejs/cli-win32-x64": "2.5.4" }, "bin": { "biome": "bin/biome" } }, "sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-D32P5HkU2Y6PySuC/WsVDTOgsDwVFmujzhhhOQjajtATpVWFDXuVd3oRbsWNSEA+aaFzyzZm22szsyydBYlSyQ=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pSEfW7B8kTsXUjUxC1xVVK+y85Ht3C5XxZ9gclmC7/3Ku9Vqz8jmI7k0p/BNIjQ6t4sFERI2sFeH73ybiZl6YQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Rpm5/AT1m+DlJmUoYvS4/vXc+0tXJPJ2NQz25TGPyHVF5JrWy75PE0GH6kVxsKtQDuCH4OgzquZq0R4kj/wCVg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-FNxojWJkL7EajAuzBgoLe0T2G0y112M4lBrDIFl/DomFTx8yqenYOIdsRLNXvOvBBofE8hJi85LjzLmBDpY7/Q=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-aby/PohmmgbShcHqFsZVzG8H6D98+P+A6xRWRrQcLW1pCjabcov5UUlke4UqNQBYTkDQav+jB4zyyDDeKB2GaA=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-emoXexPZIPAZkz2RKmA95WJUqK3I5MJNYtwEbL5ESciRzhmFMMyekDhNG8hpeOaK+ZGRDxAU4wvGuA5IHQ0h0w=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-U1jaluLw1qQc2Tx7/CeSoL9N5XcqIH+GWjpUAy1ouB5nVjSCMNO+NNHdY3RAs8zxNurLWAdj6pehQdCA2zyU+Q=="], "@ceralive/biome-config": ["@ceralive/biome-config@2026.6.2", "", {}, "sha512-LqDaJufxLOC3Rm38u/ocpUro/ub8llv1q9pcS3W/Ys7gBy+wLjS2TmcyhMjNMRg9c7pCQlPMI6eS8JkhpN+ecw=="], diff --git a/package.json b/package.json index 43d6ae5..6e8c0d4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test": "bun test" }, "devDependencies": { - "@biomejs/biome": "2.5.3", + "@biomejs/biome": "2.5.4", "@ceralive/biome-config": "2026.6.2", "@types/bun": "1.3.14", "typescript": "^5" From 6de9d03b8443f366be32a844e89ea67e7ed93bd7 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 01:48:51 -0500 Subject: [PATCH 07/14] test(transport): characterization coverage for reconnect/ordering edges --- .../src/transport/characterization.test.ts | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 control/src/transport/characterization.test.ts diff --git a/control/src/transport/characterization.test.ts b/control/src/transport/characterization.test.ts new file mode 100644 index 0000000..5634c14 --- /dev/null +++ b/control/src/transport/characterization.test.ts @@ -0,0 +1,260 @@ +// Characterization tests for the transport seam's reconnect / call-correlation / ordering +// edges — the behaviour that a later refactor (splitting transport.ts into lifecycle, +// call-dispatch, and signal modules) MUST preserve byte-for-byte at the observable level. +// +// These pin CURRENT behaviour, not aspirational behaviour: each assertion records what the +// unsplit transport.ts actually does today. If the split changes any of these observable +// facts, one of these tests goes red — which is the whole point. +// +// Like reliability.test.ts, these run against dedicated private `dbus-daemon` instances +// (each test owns one) so a destructive kill/restart is safe. That also makes the file +// self-contained — it needs no outer `dbus-run-session`. + +import { describe, expect, test } from 'bun:test'; +import { createDbusTransport } from './index'; +import { FAKE_IFACE, FAKE_PATH, startFakeService, TICK_MEMBER } from './test-support/fake-service'; +import { PrivateBus } from './test-support/private-bus'; + +const HAS_DBUS_DAEMON = Bun.which('dbus-daemon') !== null; + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +async function waitFor(predicate: () => boolean, timeoutMs: number, label: string): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (predicate()) { + return; + } + await sleep(10); + } + throw new Error(`timed out after ${timeoutMs}ms waiting for ${label}`); +} + +const tickSpec = { interface: FAKE_IFACE, member: TICK_MEMBER, path: FAKE_PATH }; + +describe.skipIf(!HAS_DBUS_DAEMON)('transport characterization', () => { + // (i) A call that times out, then the real reply lands AFTER the timeout already + // settled the promise. The late reply must be silently ignored (each `bus.invoke` + // owns its own reply closure, guarded by a `done` flag) — no crash, and no + // mis-correlation onto a later, unrelated call. + test('a reply arriving after the call already timed out is silently ignored', async () => { + const bus = new PrivateBus(); + await bus.start(); + const fake = await startFakeService({ socket: bus.socket }); + const transport = createDbusTransport({ socket: bus.socket, reconnect: { enabled: false } }); + let errorEvents = 0; + transport.on('error', () => { + errorEvents += 1; + }); + await transport.connect(); + + // SlowPing replies after 400ms; the call's own timeout is 100ms, so it times out + // first and the reply becomes a "late" one 300ms later. + await expect( + transport.callMethod({ + destination: fake.busName, + path: FAKE_PATH, + interface: FAKE_IFACE, + member: 'SlowPing', + signature: 'u', + args: [400], + timeoutMs: 100, + }), + ).rejects.toThrow('timed out after 100ms'); + + // Wait well past the 400ms reply so the ignored late reply has actually been + // delivered to (and dropped by) the settled callback. + await sleep(500); + + // No crash, no error event, still connected — the late reply disturbed nothing. + expect(errorEvents).toBe(0); + expect(transport.isConnected()).toBe(true); + + // And the pending machinery is intact: a fresh call correlates to its OWN reply, + // proving the late reply was not mis-delivered to a later promise. + const reply = await transport.callMethod({ + destination: fake.busName, + path: FAKE_PATH, + interface: FAKE_IFACE, + member: 'Ping', + }); + expect(reply.body[0]).toBe('pong'); + + await transport.disconnect(); + await fake.stop(); + await bus.stop(); + }); + + // (ii) A bus drop while a call is in flight. The in-flight call must reject, and the + // observed ordering is pinned: the `disconnected` event is delivered BEFORE the call + // rejection is observed. (In `#handleDrop`, pending calls are rejected and then + // `disconnected` is emitted synchronously — but a promise rejection is observed on a + // microtask, so the synchronous event listener runs first.) + test('a mid-call bus drop rejects the in-flight call after emitting disconnected', async () => { + const bus = new PrivateBus(); + await bus.start(); + const fake = await startFakeService({ socket: bus.socket }); + const transport = createDbusTransport({ socket: bus.socket, reconnect: { enabled: false } }); + + const order: string[] = []; + let rejection: unknown = null; + transport.on('disconnected', () => order.push('disconnected')); + await transport.connect(); + + // A call that will never get a reply — the bus dies under it. + const call = transport + .callMethod({ + destination: fake.busName, + path: FAKE_PATH, + interface: FAKE_IFACE, + member: 'SlowPing', + signature: 'u', + args: [5000], + }) + .catch((error: unknown) => { + order.push('call-rejected'); + rejection = error; + }); + + // Give the call time to reach the wire, then drop the bus under it. + await sleep(30); + bus.kill(); + + await waitFor( + () => order.includes('disconnected') && order.includes('call-rejected'), + 5000, + 'disconnected + call rejection', + ); + await call; + + // Pinned ordering: the event precedes the observed rejection. + expect(order).toEqual(['disconnected', 'call-rejected']); + // Pinned rejection type: a DisconnectedError (the connection-end drop cause). + expect(rejection).toBeInstanceOf(Error); + expect((rejection as Error).name).toBe('DisconnectedError'); + + await transport.disconnect(); + // The fake died with its bus; stopping it would write to a closed stream. Leave it. + await bus.stop(); + }); + + // (iii) A drop that arrives while a reconnect is already running. The reconnect loop is + // idempotent: `#handleDrop` early-returns whenever the state is already `disconnected` + // or `reconnecting`, so repeated low-level drop signals never spawn a second reconnect + // loop. The transport converges to a single connected state — exactly one `disconnected` + // and one `reconnected`, no error, and it is not wedged. + test('a drop during an in-flight reconnect does not double-schedule or wedge', async () => { + const bus = new PrivateBus(); + await bus.start(); + let fake = await startFakeService({ socket: bus.socket }); + const transport = createDbusTransport({ + socket: bus.socket, + reconnect: { initialDelayMs: 25, maxDelayMs: 100 }, + }); + + const events: string[] = []; + let errorEvents = 0; + transport.on('disconnected', () => events.push('disconnected')); + transport.on('reconnected', () => events.push('reconnected')); + transport.on('error', () => { + errorEvents += 1; + }); + await transport.connect(); + + const ticks: bigint[] = []; + const subscription = await transport.subscribeSignal(tickSpec, (event) => { + ticks.push(event.body[0] as bigint); + }); + + fake.emitTick(11n); + await waitFor(() => ticks.includes(11n), 3000, 'pre-drop tick'); + + // Drop the bus and leave it down long enough for the reconnect loop to spin through + // several failed establish attempts before the bus returns. + bus.kill(); + await waitFor(() => events.includes('disconnected'), 5000, 'disconnected'); + await sleep(150); + await bus.start(); + await waitFor(() => events.includes('reconnected'), 15000, 'reconnected'); + + // A settle window to catch any spurious extra event from a double-scheduled loop. + await sleep(300); + + expect(events.filter((event) => event === 'disconnected')).toHaveLength(1); + expect(events.filter((event) => event === 'reconnected')).toHaveLength(1); + expect(errorEvents).toBe(0); + + // Not wedged: a fresh producer's signal flows through the auto-resubscribed rule. + fake = await startFakeService({ socket: bus.socket }); + fake.emitTick(22n); + await waitFor(() => ticks.includes(22n), 8000, 'post-reconnect tick'); + expect(transport.subscriptionCount()).toBe(1); + + await subscription.unsubscribe(); + await transport.disconnect(); + await fake.stop(); + await bus.stop(); + }, 30000); + + // (iv) A subscription added AND one removed while a reconnect is in progress. Because + // mutating a subscription while disconnected only touches the in-memory match-rule + // refcount (the bus call is skipped when not connected), and `#establish()` re-issues + // every live rule on reconnect, the refcounting must end up correct: the added + // subscription is registered (receives signals) and the removed one is not. + test('subscriptions mutated during reconnect end up correctly (un)registered', async () => { + const bus = new PrivateBus(); + await bus.start(); + const transport = createDbusTransport({ + socket: bus.socket, + reconnect: { initialDelayMs: 25, maxDelayMs: 100 }, + }); + + const events: string[] = []; + transport.on('disconnected', () => events.push('disconnected')); + transport.on('reconnected', () => events.push('reconnected')); + await transport.connect(); + + // `removed` is a path-filtered rule; `added` is a distinct (no-path) rule that still + // matches the same emitted Tick — so their match-rule strings differ and are tracked + // independently. + const removedTicks: bigint[] = []; + const addedTicks: bigint[] = []; + const removed = await transport.subscribeSignal(tickSpec, (event) => { + removedTicks.push(event.body[0] as bigint); + }); + expect(transport.subscriptionCount()).toBe(1); + + // Drop the bus; while the reconnect loop is running, mutate the subscription set. + bus.kill(); + await waitFor(() => events.includes('disconnected'), 5000, 'disconnected'); + + await removed.unsubscribe(); + const added = await transport.subscribeSignal( + { interface: FAKE_IFACE, member: TICK_MEMBER }, + (event) => { + addedTicks.push(event.body[0] as bigint); + }, + ); + expect(transport.subscriptionCount()).toBe(1); + + // Bring the bus back; `#establish()` re-issues exactly the rules still in the + // refcount map — the `added` one, not the `removed` one. + await bus.start(); + await waitFor(() => events.includes('reconnected'), 15000, 'reconnected'); + + const fake = await startFakeService({ socket: bus.socket }); + fake.emitTick(33n); + await waitFor(() => addedTicks.includes(33n), 8000, 'added-subscription tick'); + + // Grace to prove the removed subscription genuinely receives nothing. + await sleep(200); + expect(addedTicks).toEqual([33n]); + expect(removedTicks).toEqual([]); + expect(transport.subscriptionCount()).toBe(1); + + await added.unsubscribe(); + await transport.disconnect(); + await fake.stop(); + await bus.stop(); + }, 30000); +}); From d75863f560d08f93e3270f10d6dc22eee40bee28 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 01:51:51 -0500 Subject: [PATCH 08/14] test(ports): AST-based forbidden-surface detector (compiler API), negative fixtures --- control/src/ports/forbidden-surface.test.ts | 187 ++++++++++++++++++-- 1 file changed, 174 insertions(+), 13 deletions(-) diff --git a/control/src/ports/forbidden-surface.test.ts b/control/src/ports/forbidden-surface.test.ts index 6209846..4c2ec25 100644 --- a/control/src/ports/forbidden-surface.test.ts +++ b/control/src/ports/forbidden-surface.test.ts @@ -3,13 +3,22 @@ // The single most safety-critical constraint in the package (Must-NOT-Have: "no MM // Simple.Connect / CreateBearer / Bearer.Connect calls ever"). Interfaces are // erased at runtime, so the enforcement is a source scan: every port `.ts` file is -// stripped of comments (so prose mentions of "bearer" never trip it) and checked -// for a method declaration whose name is `connect`, `simpleConnect`, or contains -// "bearer". Adding such a method to any port fails this test — and therefore CI. +// parsed with the TypeScript compiler API and each DECLARED member of an interface, +// type literal, or class is checked for a name that is `connect`, `simpleConnect`, +// or contains "bearer". Adding such a member to any port fails this test — and CI. +// +// The detector walks the real AST rather than the source text, so it catches shapes +// a naive regex cannot: method signatures, property signatures typed as function +// types, get/set accessors, optional members, and quoted or computed string-literal +// member names (`'connect'()`, `['connect']()`) all resolve to their true name. It +// only ever inspects member NAMES on those three declaration kinds — never string +// literal values, value-position object-literal keys, comments, local variables, or +// call expressions — so prose mentions of "bearer" and value keys never trip it. import { expect, test } from 'bun:test'; import { readdirSync } from 'node:fs'; import { join } from 'node:path'; +import * as ts from 'typescript'; const portsDir = import.meta.dir; @@ -18,11 +27,82 @@ function isForbiddenMethodName(name: string): boolean { return lower === 'connect' || lower === 'simpleconnect' || lower.includes('bearer'); } +/** + * Resolve a declared member's name node to its static string name, or `undefined` + * when the name is not statically knowable (e.g. a computed `[Symbol.iterator]` or a + * computed reference to a non-literal identifier). Plain identifiers, private names, + * string / numeric literals, and computed names wrapping a string / numeric literal + * (`['connect']`) all resolve to their real text. + */ +function resolveMemberName(name: ts.Node): string | undefined { + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) { + return name.text; + } + if (ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) { + return name.text; + } + if (ts.isComputedPropertyName(name)) { + const { expression } = name; + if (ts.isStringLiteralLike(expression) || ts.isNumericLiteral(expression)) { + return expression.text; + } + } + return undefined; +} + +/** + * Every DECLARED member name of an interface, type literal, or class in `source`, + * parsed via the TypeScript compiler API. Value-position object-literal keys, string + * literal values, comments, local variables, and call expressions are all excluded — + * only the port's actual type surface is inspected. Method signatures, property + * signatures (including arrow-typed properties), get/set accessors, optional members, + * and quoted / computed string-literal member names are all resolved to their name. + */ +function declaredMemberNames(source: string): string[] { + const sourceFile = ts.createSourceFile( + 'port.ts', + source, + ts.ScriptTarget.Latest, + /* setParentNodes */ true, + ); + const names: string[] = []; + + const collectMembers = (members: readonly (ts.TypeElement | ts.ClassElement)[]): void => { + for (const member of members) { + if (member.name === undefined) { + continue; + } + const resolved = resolveMemberName(member.name); + if (resolved !== undefined) { + names.push(resolved); + } + } + }; + + const visit = (node: ts.Node): void => { + if ( + ts.isInterfaceDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isClassExpression(node) || + ts.isTypeLiteralNode(node) + ) { + collectMembers(node.members); + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return names; +} + +// The retired regex detector — kept ONLY so the differential test below can prove, +// by running it, that the AST rebuild is a strict improvement (it caught just one of +// the seven forbidden shapes). Not used by any real-port scan; do not reintroduce. function stripComments(source: string): string { return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, ''); } -function declaredMemberNames(source: string): string[] { +function legacyRegexMemberNames(source: string): string[] { const names: string[] = []; const methodDecl = /(?:^|\n)\s*([a-zA-Z_$][\w$]*)\s*[<(]/g; let match = methodDecl.exec(source); @@ -44,14 +124,14 @@ function portSourceFiles(): string[] { test('no port source declares a bearer / connect method', async () => { for (const file of portSourceFiles()) { - const source = stripComments(await Bun.file(join(portsDir, file)).text()); + const source = await Bun.file(join(portsDir, file)).text(); const forbidden = declaredMemberNames(source).filter(isForbiddenMethodName); - expect(forbidden, `forbidden method(s) in ${file}: ${forbidden.join(', ')}`).toEqual([]); + expect(forbidden, `forbidden member(s) in ${file}: ${forbidden.join(', ')}`).toEqual([]); } }); test('the ModemManager port source declares the expected non-bearer mutations', async () => { - const source = stripComments(await Bun.file(join(portsDir, 'modem-manager.ts')).text()); + const source = await Bun.file(join(portsDir, 'modem-manager.ts')).text(); const names = declaredMemberNames(source); for (const expected of [ 'setRadioModes', @@ -66,15 +146,96 @@ test('the ModemManager port source declares the expected non-bearer mutations', } }); -test('the detector actually catches a bearer / connect method (self-test)', () => { +test('the detector catches every forbidden member shape (self-test)', () => { + // Each line is a DISTINCT declaration shape the AST resolver must catch: a plain + // method, a computed string-literal method, a quoted string-literal method, an + // optional method, a get accessor, a set accessor, and an arrow-typed property. + // `setRadioModes` is a legitimate mutation that must pass through unflagged. const rogue = ` - export interface Rogue { + export interface RogueSurface { connect(): Promise; - createBearer(): Promise; - bearerConnect(): Promise; + ['simpleConnect'](): void; + 'createBearer'(): void; + bearerReset?(): void; + get bearerState(): number; + set bearerTarget(value: number); + bearerHook: () => Promise; + setRadioModes(): Promise; + } + `; + const flagged = declaredMemberNames(rogue).filter(isForbiddenMethodName).sort(); + expect(flagged).toEqual([ + 'bearerHook', + 'bearerReset', + 'bearerState', + 'bearerTarget', + 'connect', + 'createBearer', + 'simpleConnect', + ]); + // The acceptance bar for the rebuild: at least seven distinct forbidden shapes. + expect(flagged.length).toBeGreaterThanOrEqual(7); +}); + +test('the detector ignores string values, value-position keys, and non-forbidden members', () => { + // Three negative fixtures that must NOT trip the detector: + // 1. `reconnectPolicy` — a real interface member whose name merely CONTAINS the + // substring "connect"; the predicate matches "connect" exactly, not as a + // substring, so it must pass (proves the predicate is not overly broad). + // 2. `description: '…bearer…'` — a member whose string-literal VALUE contains + // "bearer"; only member NAMES are inspected, never string content. + // 3. `bearerConnect: true` — a key in a value-position object literal, not a + // type-level member declaration; the AST never walks object-literal keys. + const negative = ` + interface LegitPort { + reconnectPolicy: RetryPolicy; setRadioModes(): Promise; + description: 'manages bearer state internally'; + } + const runtimeConfig = { + bearerConnect: true, + description: 'manages bearer state internally', + }; + `; + const members = declaredMemberNames(negative); + expect(members).toContain('reconnectPolicy'); + expect(members).not.toContain('bearerConnect'); + expect(members.filter(isForbiddenMethodName)).toEqual([]); +}); + +test('the AST detector catches member shapes the legacy regex missed (differential)', () => { + // The same rogue surface, run through BOTH detectors. The retired regex only ever + // matched an identifier immediately followed by `(` or `<` at a line start, so it + // caught the single plain method and missed the other six shapes. The AST walk + // catches all seven — the strict improvement the rebuild delivers. + const rogue = ` + export interface RogueSurface { + connect(): Promise; + ['simpleConnect'](): void; + 'createBearer'(): void; + bearerReset?(): void; + get bearerState(): number; + set bearerTarget(value: number); + bearerHook: () => Promise; } `; - const flagged = declaredMemberNames(stripComments(rogue)).filter(isForbiddenMethodName).sort(); - expect(flagged).toEqual(['bearerConnect', 'connect', 'createBearer']); + const legacyFlagged = legacyRegexMemberNames(stripComments(rogue)) + .filter(isForbiddenMethodName) + .sort(); + const astFlagged = declaredMemberNames(rogue).filter(isForbiddenMethodName).sort(); + + // The regex caught only the plain method. + expect(legacyFlagged).toEqual(['connect']); + // The AST catches every shape the regex missed, and then some. + for (const missed of [ + 'bearerHook', + 'bearerReset', + 'bearerState', + 'bearerTarget', + 'createBearer', + 'simpleConnect', + ]) { + expect(legacyFlagged).not.toContain(missed); + expect(astFlagged).toContain(missed); + } }); From 3181929707e1cb7710152b499f00c2c9e9baa3f9 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 01:52:21 -0500 Subject: [PATCH 09/14] docs(fm350): tracked decision record (PCIe, deferred); parse 1.24.2 version case --- control/src/backend/device-classifier.ts | 8 + control/src/backend/features.test.ts | 4 + docs/FM350-DECISION.md | 190 +++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 docs/FM350-DECISION.md diff --git a/control/src/backend/device-classifier.ts b/control/src/backend/device-classifier.ts index 333a7c3..f5d13b9 100644 --- a/control/src/backend/device-classifier.ts +++ b/control/src/backend/device-classifier.ts @@ -9,6 +9,14 @@ // `mm-managed`; a bare vendor-specific interface with no recognized driver is NOT a // modem. `pending-modeswitch` is a DISTINCT state (a modem installer awaiting // `usb_modeswitch`), never conflated with `unmanaged`. +// +// SCOPE — USB ONLY: the whole input here is a `UsbDeviceSnapshot`, a udev/sysfs view of a +// USB device. PCIe modems are out of scope by construction and get NO entry in this model — +// a PCI `vendor:device` pair is never smuggled in as a pseudo-USB identity. The Fibocom +// FM350 is the canonical example: it is a PCIe module (PCI `14c3:4d75`, bound by the +// `mtk_t7xx` driver on the `wwan`/`net` subsystems, with no USB VID:PID), so it is +// documented-deferred rather than classified here. See `docs/FM350-DECISION.md` for the +// evidence and the three-gate ledger behind that decision. import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode'; diff --git a/control/src/backend/features.test.ts b/control/src/backend/features.test.ts index 12c0f7d..3292ed4 100644 --- a/control/src/backend/features.test.ts +++ b/control/src/backend/features.test.ts @@ -22,6 +22,10 @@ describe('parseMmVersion', () => { expect(parseMmVersion('1.24.0')).toEqual({ major: 1, minor: 24 }); }); + test('parses the 1.24.2 FM350-fix release', () => { + expect(parseMmVersion('1.24.2')).toEqual({ major: 1, minor: 24 }); + }); + test('parses major.minor without a patch', () => { expect(parseMmVersion('1.20')).toEqual({ major: 1, minor: 20 }); }); diff --git a/docs/FM350-DECISION.md b/docs/FM350-DECISION.md new file mode 100644 index 0000000..02f0925 --- /dev/null +++ b/docs/FM350-DECISION.md @@ -0,0 +1,190 @@ +# FM350 decision record — PCIe modem, documented-deferred + +**Status:** documented-deferred. This repo does **not** implement Fibocom FM350 device +enablement, and adds **no** entry for it to the USB device classifier. The FM350 is a PCIe +device; the reasons and the future-decision gates are recorded below so the deferral is a +deliberate, evidence-backed choice rather than an oversight. + +This is a **tracked** engineering decision record (committed to git), not a claim that the +FM350 works on any CeraLive device. Nothing here has been exercised on real hardware. + +## What the FM350 is + +The Fibocom FM350-GL is an M.2 **PCIe** WWAN module built on the MediaTek T700/T7xx +platform (Lenovo ships it in ThinkPad laptops). Its bus identity is the **PCI** +vendor:device pair `0x14c3:0x4d75` (`14c3` = MediaTek), and the Linux kernel binds it with +the **`mtk_t7xx`** PCIe WWAN driver, which exposes the modem on the `wwan`/`net` subsystems. +It is **not** a USB modem and presents **no** USB VID:PID. + +## The mechanical rule + +Given the question *"does this repo add FM350 device enablement to `device-classifier.ts`?"*, +apply this decision procedure against the **verified upstream ModemManager 1.24.2 source**: + +1. Extract, verbatim from the MM source, how the plugin layer matches the FM350. +2. **IF** the source matches the device by a **USB VID:PID** (a USB-bus identity): + - the device is in scope for `device-classifier.ts` (which is a **USB-only** model); + - **sub-branch — contrary-evidence HARD STOP:** if the source names an *explicit FM350 + USB VID:PID*, do not improvise a classifier representation — stop and surface the exact + citation to a human; any classifier change becomes a user decision. +3. **ELSE IF** the source matches the device by a **PCIe identity** (a PCI vendor:device + pair gated on a PCIe kernel driver + `wwan`/`net` subsystems, with no USB VID:PID): + - the device is **out of scope** for the USB-only classifier; + - the outcome is **documented-deferred** — record the decision here, add **no** classifier + entry, add **no** code branch, add **no** fixture. + +### Which branch fired + +**Branch 3 (PCIe identity) fired.** The MM 1.24.2 source matches the FM350 exclusively by the +PCI pair `0x14c3:0x4d75`, gated on the `mtk_t7xx` PCIe WWAN kernel driver and the `wwan`/`net` +subsystems. No USB VID:PID for the FM350 exists anywhere in the source tree. The +contrary-evidence HARD STOP was therefore **not** triggered. Outcome: **documented-deferred**, +with zero change to the USB device classifier. + +## Verified evidence + +### Provenance — the exact tarball these facts come from + +- Artifact: `modemmanager_1.24.2.orig.tar.xz` +- Source of truth: `packaging/upstream-pins.yaml` (`sources.modemmanager.orig_tar_url` / + `orig_tar_sha256`), the provenance-verified pin landed in this repo's upstream-currency work. +- Download URL: + `https://deb.debian.org/debian/pool/main/m/modemmanager/modemmanager_1.24.2.orig.tar.xz` +- **sha256 (re-downloaded and re-verified for this record):** + `8f575bfdcc0277b68946a65b527a804be8051abbb17430b6108da365a92c6913` + — byte-for-byte equal to the pinned `orig_tar_sha256`. + +All file:line citations below are relative to the extracted tree root +`modemmanager-1.24.2/`. + +### Citation 1 — the PCI vendor:device match (`0x14c3:0x4d75`) + +`src/plugins/mtk/mm-plugin-mtk.c`, lines 49-50 (inside `create_modem`): + +```c + /* FM350 support with Fibocom-specific changes */ + if (vendor == 0x14c3 && product == 0x4d75) { +``` + +This confirms the expected pair exactly: `vendor == 0x14c3 && product == 0x4d75`. + +### Citation 2 — the `mtk_t7xx` driver requirement and the `wwan`/`net` subsystems + +`src/plugins/mtk/mm-plugin-mtk.c`, lines 82-83 and 88-89 (inside `mm_plugin_create_mtk`): + +```c + static const gchar *subsystems[] = { "wwan", "net", NULL }; + static const gchar *drivers[] = { "mtk_t7xx", NULL }; + ... + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, +``` + +The plugin only ever binds devices on the `wwan`/`net` subsystems whose kernel driver is +`mtk_t7xx` (the MediaTek T7xx **PCIe** WWAN driver). + +### Citation 2b — the match is PCIe, not USB + +`src/plugins/mtk/mm-plugin-mtk.c`, `create_modem` signature (lines 42-43) receives the +**PCI** subsystem identifiers, which are only ever populated for PCI devices: + +```c + guint16 subsystem_vendor, + guint16 subsystem_device, +``` + +The mtk plugin declares **no** `MM_PLUGIN_ALLOWED_VENDOR_IDS` — the USB-style vendor-id +table. It matches purely by PCIe driver + subsystem. (For contrast, USB plugins publish a +USB vendor-id table, e.g. the fibocom plugin below.) + +### Citation 3 — the fibocom plugin carries NO FM350 USB VID:PID (the contrary-evidence check) + +A whole-directory search of the USB-based fibocom plugin returns nothing for the FM350: + +``` +$ grep -rniI '14c3|4d75|fm350|t7xx' src/plugins/fibocom/ +(no matches) +``` + +For contrast, the fibocom plugin matches its own **USB** devices by a USB vendor-id table, +`src/plugins/fibocom/mm-plugin-fibocom.c`, lines 113-114: + +```c + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x2cb7, 0x1782, 0x1508, 0 }; +``` + +None of `0x2cb7`, `0x1782`, `0x1508` is `0x14c3`; there is no `0x4d75`. The FM350 is not a +fibocom-USB device in this source. + +### Citation 4 — every `14c3`/`4d75` occurrence in the whole tree + +``` +$ grep -rniI '14c3|4d75' . +data/dispatcher-fcc-unlock/14c3:7:# Lenovo-shipped Fibocom FM350-GL (14c3:4d75) FCC unlock +data/dispatcher-fcc-unlock/meson.build:16: '14c3', +data/dispatcher-fcc-unlock/meson.build:33: '14c3:4d75': '14c3', +src/plugins/mtk/mm-plugin-mtk.c:50: if (vendor == 0x14c3 && product == 0x4d75) { +``` + +Only three places name the FM350: the PCIe mtk plugin match, and an FCC-unlock dispatcher +keyed on `14c3:4d75` plus its meson wiring. None is a USB identity. + +### Citation 5 — the NEWS entry that introduced this plugin + +`NEWS`, line 168: + +``` + ** mtk: new plugin with MBIM support for t7xx devices (eg FM350, L850, etc) +``` + +The FM350 is handled by the (PCIe) `mtk` plugin's T7xx path — corroborating the source-level +findings above. + +## Three-gate ledger + +FM350 device enablement upstream requires three independent gates. This record is honest +about each: only gate 1 is verified in this repo; gates 2 and 3 are untested. + +| # | Gate | Requirement | State | Basis | +|---|------|-------------|-------|-------| +| 1 | MM version floor | ModemManager **≥ 1.24.2** (the release carrying the mtk-plugin FM350 fixes) | **CLEARED** | This repo now ships ModemManager **1.24.2** — see `packaging/upstream-pins.yaml` (`sources.modemmanager.upstream_tag: "1.24.2"`). The version-floor gate is satisfied. | +| 2 | Kernel | The `mtk_t7xx` PCIe WWAN driver present and enumerating the module over PCIe on the target hardware | **OPEN** | Not validated on real bench hardware in this work. No target-kernel `mtk_t7xx` bring-up has been performed or observed. | +| 3 | HIL (hardware-in-the-loop) | A physical FM350 module probed end-to-end through the stack on a bench device | **OPEN** | No physical FM350 unit has been tested. There is no hardware evidence of any kind. | + +Gate 1 being CLEARED does **not** imply the FM350 works — it only removes the upstream +version-floor obstacle. Gates 2 and 3 remain the blocking, hardware-gated unknowns, and are +deliberately left OPEN rather than assumed. + +## No USB classifier entry — and why + +**No USB classifier entry exists or is added for the FM350.** `control/src/backend/device-classifier.ts` +is, by its own top-of-file docstring, a **USB-only** model: its whole input is a +`UsbDeviceSnapshot` (a udev/sysfs view of a **USB** device — `vendorId`/`productId` are USB +identifiers), and it classifies into exactly four classes: +`mm-managed` / `router-mode` / `unmanaged` / `pending-modeswitch`. + +The FM350 is a **PCIe** device with **no** USB VID:PID (Citations 1-4). Representing a PCI +`vendor:device` pair as a pseudo-USB identity in the USB-only classifier would be a +fabrication — it would violate the classifier's own honesty rule (an ambiguous or +unrecognized descriptor set must return `unmanaged` with a truthful reason, never a +confident-sounding wrong class). Therefore: + +- No classifier entry, branch, type, or fixture is added for the FM350. +- The classifier's four classes are unchanged. There is **no** fifth "deferred" class and + none is added — "deferred" is the status of *this decision record*, never a device class. +- If a mis-fed FM350-shaped descriptor were ever handed to the USB-only classifier, the + correct, truthful result is `unmanaged` (an unrecognized device), with no mode-switch + recommendation. That behavior is exercised by the negative-drill test whose output is + captured as QA evidence under `test-results/upstream-currency/2.5/` (the + unmanaged-classification proof). + +A doc comment in `device-classifier.ts` cross-references this record so future readers +understand why PCIe modems like the FM350 are intentionally out of the USB-only model. + +## If this is revisited + +Enabling the FM350 is a **future decision**, not a code change to make here. It would begin +by closing gate 2 (target-kernel `mtk_t7xx` PCIe enumeration) and gate 3 (a real HIL run), +and would live wherever PCIe/`wwan`-subsystem modems are modeled — **not** as a pseudo-USB +entry in this USB-only classifier. From 29d9f70ac40659718be9533003f45caf307a1b04 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 02:03:55 -0500 Subject: [PATCH 10/14] ci(release): peeled-SHA pinning, env-quarantined inputs, build-before-publish, integrity-idempotent npm, staged sanitized assets --- .github/workflows/release.yml | 395 ++++++++++++++++++---- packaging/README.md | 4 +- packaging/ci/generate-release-manifest.sh | 113 +++++-- packaging/ci/reconcile-release-assets.sh | 241 +++++++++++++ packaging/ci/resolve-tag.sh | 77 +++++ 5 files changed, 734 insertions(+), 96 deletions(-) create mode 100755 packaging/ci/reconcile-release-assets.sh create mode 100755 packaging/ci/resolve-tag.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6193c43..225e0d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,11 +4,22 @@ name: Release # @ceralive/modem-control AND the ModemManager-stack .debs. There is deliberately NO # separate release-packaging workflow. Manually dispatched with the unified SemVer tag. # -# Job graph (each gates the next): -# tag-guard — input MUST be exactly vX.Y.Z, else FAIL CLOSED before anything else runs -# test — full bun lane + packaging contract lane (test-before-publish) -# publish-npm — OIDC trusted publishing of @ceralive/modem-control@X.Y.Z (version verified) -# build-deb — inject -~ceraliveX.Y.Z into each source changelog, upload +# Job graph (STRICTLY sequential — each gates the next): +# tag-guard — resolve the input tag to its immutable PEELED commit SHA, then validate the +# tag shape (vX.Y.Z). Fails closed before anything else runs. Exports version+sha. +# test — full bun lane + packaging contract lane (test-before-publish). +# build-deb — build the .deb set (both arches), contract + smoke, per-release manifest. +# publish-npm — integrity-idempotent OIDC trusted publish of @ceralive/modem-control@X.Y.Z. +# create-release — stage sanitized assets, immutably reconcile the GitHub release. +# +# SECURITY MODEL: +# * Every job checks out the RESOLVED peeled commit SHA (never the mutable tag ref) and +# asserts `git rev-parse HEAD` matches it. The resolution lives in ONE shared helper, +# packaging/ci/resolve-tag.sh, used by tag-guard, publish-npm's pre-publish TOCTOU re-check, +# and create-release's pre-create re-check. +# * NO `${{ … }}` expression is ever interpolated directly into a `run:` shell body. Every +# dynamic value (esp. github.event.inputs.tag) enters `run:` ONLY via `env:` and is used as +# a quoted "$VAR". on: workflow_dispatch: inputs: @@ -27,29 +38,82 @@ concurrency: jobs: tag-guard: - name: Tag guard (fail closed on non-vX.Y.Z) + name: Tag guard + resolve peeled SHA (fail closed) runs-on: ubuntu-latest outputs: version: ${{ steps.guard.outputs.version }} + sha: ${{ steps.resolve.outputs.sha }} steps: - - uses: actions/checkout@v7 + # STEP 0 — bootstrap checkout pinned to the IMMUTABLE workflow commit. Its sole purpose is + # to obtain packaging/ci/resolve-tag.sh: tag-guard cannot consume its own `needs` output, + # and a fresh runner has no repo files before any checkout runs. + - name: Bootstrap checkout (immutable workflow commit) + uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + + # STEP 1 — resolve the input tag to its PEELED commit SHA via the shared helper (an + # explicit repository URL; prefers refs/tags/^{}; fails closed if absent/ambiguous). + - name: Resolve tag -> peeled commit SHA + id: resolve + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + RELEASE_TAG: ${{ github.event.inputs.tag }} + run: | + sha="$(bash packaging/ci/resolve-tag.sh "$REPO_URL" "$RELEASE_TAG")" + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + echo "Resolved ${RELEASE_TAG} -> peeled commit ${sha}" - # tag-guard.sh accepts ONLY ^v[0-9]+\.[0-9]+\.[0-9]+$ and echoes the stripped X.Y.Z. - # A pre-release (-rc.1), build metadata (+build5), or missing-v tag exits non-zero, - # failing this job — and therefore the whole release — before any publish runs. - - name: Validate release tag + # STEP 2 — re-checkout PINNED to the resolved commit (the immutable target, not the tag ref). + - name: Checkout the resolved commit + uses: actions/checkout@v7 + with: + ref: ${{ steps.resolve.outputs.sha }} + + # STEP 3 — assert the working tree is exactly the resolved SHA. + - name: Assert checkout is pinned to the resolved SHA + env: + EXPECTED_SHA: ${{ steps.resolve.outputs.sha }} + run: | + head="$(git rev-parse HEAD)" + if [ "$head" != "$EXPECTED_SHA" ]; then + echo "::error::HEAD ${head} != resolved ${EXPECTED_SHA}" + exit 1 + fi + echo "HEAD pinned to ${head}" + + # STEP 4 — the tag-shape contract from the pinned checkout. tag-guard.sh accepts ONLY + # ^v[0-9]+\.[0-9]+\.[0-9]+$ and echoes the stripped X.Y.Z. A pre-release (-rc.1), build + # metadata (+build5), or missing-v tag exits non-zero, failing the whole release. + - name: Validate release tag (vX.Y.Z) id: guard + env: + RELEASE_TAG: ${{ github.event.inputs.tag }} run: | - version="$(bash packaging/ci/tag-guard.sh "${{ github.event.inputs.tag }}")" + version="$(bash packaging/ci/tag-guard.sh "$RELEASE_TAG")" echo "version=${version}" >> "$GITHUB_OUTPUT" - echo "Release tag OK: ${{ github.event.inputs.tag }} -> ${version}" + echo "Release tag OK: ${RELEASE_TAG} -> ${version}" test: name: Test before publish (bun + packaging) needs: tag-guard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - name: Checkout the resolved commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.tag-guard.outputs.sha }} + + - name: Assert checkout is pinned to the resolved SHA + env: + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + run: | + head="$(git rev-parse HEAD)" + if [ "$head" != "$EXPECTED_SHA" ]; then + echo "::error::HEAD ${head} != resolved ${EXPECTED_SHA}" + exit 1 + fi + echo "HEAD pinned to ${head}" - uses: oven-sh/setup-bun@v2 with: @@ -82,51 +146,6 @@ jobs: - name: Packaging contract lane run: bash packaging/ci/contract.sh - publish-npm: - name: Publish @ceralive/modem-control (OIDC trusted publishing) - needs: [tag-guard, test] - permissions: - contents: read - id-token: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.14 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - registry-url: https://registry.npmjs.org/ - - # OIDC trusted publishing needs npm >= 11.5.1 (house pattern pins 11.18.0). - - name: Install pinned trusted-publishing npm - run: npm install -g npm@11.18.0 - - # npm side is VERIFIED (not injected): control/package.json version must equal the - # release tag's X.Y.Z, else fail closed before publish. - - name: Verify package version matches the release tag - working-directory: control - run: | - PKG="$(node -p "require('./package.json').version")" - if [ "$PKG" != "${{ needs.tag-guard.outputs.version }}" ]; then - echo "::error::control/package.json version ($PKG) != release tag (${{ needs.tag-guard.outputs.version }})" - exit 1 - fi - echo "npm version provenance OK: $PKG" - - - name: Install workspace (frozen lockfile) - run: bun install --frozen-lockfile - - - name: Publish via OIDC trusted publishing - working-directory: control - env: - # Provenance attestations only when the source repo is public. - NPM_CONFIG_PROVENANCE: ${{ !github.event.repository.private }} - run: npm publish --access public - build-deb: name: Build .deb set + contract + daemon smoke (real artifacts) needs: [tag-guard, test] @@ -135,7 +154,21 @@ jobs: # host docker daemon, not to be a container itself. runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - name: Checkout the resolved commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.tag-guard.outputs.sha }} + + - name: Assert checkout is pinned to the resolved SHA + env: + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + run: | + head="$(git rev-parse HEAD)" + if [ "$head" != "$EXPECTED_SHA" ]; then + echo "::error::HEAD ${head} != resolved ${EXPECTED_SHA}" + exit 1 + fi + echo "HEAD pinned to ${head}" # arm64 .debs are built under full-system QEMU (never cross-compiled); register binfmt. - name: Set up QEMU (arm64 emulation) @@ -144,7 +177,7 @@ jobs: # Real rebuilds. RELEASE_VERSION injects -~ceraliveX.Y.Z into a COPY of # each source's debian/changelog inside the container (the committed tree is never # mutated), builds in bootstrap order on native amd64 + QEMU arm64, and asserts the - # 9-package runtime closure per arch. --force-bad-version is applied by inject-deb-version. + # per-source package sets. --force-bad-version is applied by inject-deb-version. - name: Build the MM 1.24 stack (.deb) — amd64 + arm64 env: RELEASE_VERSION: ${{ github.event.inputs.tag }} @@ -161,17 +194,19 @@ jobs: packaging/ci/test-package-contract.sh arm64 # Daemon smoke: system D-Bus + polkit + NetworkManager 1.42, start MM, busctl - # introspect the root ObjectManager, mmcli --version == 1.24.0, udev/FCC/GIR/Vala paths. + # introspect the root ObjectManager, mmcli --version == pinned tag, udev/FCC/GIR/Vala paths. - name: Daemon smoke (amd64) run: packaging/ci/daemon-smoke.sh amd64 - # Per-release manifest: the release tag -> the 9 runtime deb versions per arch - # (Phase-B apt publication consumes this as its package -> source -> version matrix). + # Manifest-complete per-release manifest: a checksum row for EVERY built deb (both arches), + # runtime closure marked; fails closed if the produced set != the frozen all-artifact sets. - name: Generate release manifest - run: packaging/ci/generate-release-manifest.sh "${{ github.event.inputs.tag }}" + env: + RELEASE_TAG: ${{ github.event.inputs.tag }} + run: bash packaging/ci/generate-release-manifest.sh "$RELEASE_TAG" - # Upload the REAL built .debs (both arches: 9 runtime + dev/gir/dbgsym) + the manifest as - # CI release artifacts. Bench devices download this set and `apt install ./*.deb`. + # Upload the REAL built .debs (both arches: runtime + dev/gir/dbgsym) + the manifest as a + # CI artifact, consumed by create-release for GitHub Release asset reconciliation. - name: Upload .deb artifacts + release manifest uses: actions/upload-artifact@v7 with: @@ -181,3 +216,229 @@ jobs: packaging/build/amd64/*.deb packaging/build/arm64/*.deb if-no-files-found: error + + publish-npm: + name: Publish @ceralive/modem-control (integrity-idempotent OIDC) + needs: [tag-guard, build-deb] + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout the resolved commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.tag-guard.outputs.sha }} + + - name: Assert checkout is pinned to the resolved SHA + env: + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + run: | + head="$(git rev-parse HEAD)" + if [ "$head" != "$EXPECTED_SHA" ]; then + echo "::error::HEAD ${head} != resolved ${EXPECTED_SHA}" + exit 1 + fi + echo "HEAD pinned to ${head}" + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.14 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + + # OIDC trusted publishing needs npm >= 11.5.1 (house pattern pins 11.18.0). + - name: Install pinned trusted-publishing npm + run: npm install -g npm@11.18.0 + + # npm side is VERIFIED (not injected): control/package.json version must equal the release + # tag's X.Y.Z, else fail closed before publish. + - name: Verify package version matches the release tag + working-directory: control + env: + EXPECTED_VERSION: ${{ needs.tag-guard.outputs.version }} + run: | + pkg="$(node -p "require('./package.json').version")" + if [ "$pkg" != "$EXPECTED_VERSION" ]; then + echo "::error::control/package.json version ($pkg) != release tag ($EXPECTED_VERSION)" + exit 1 + fi + echo "npm version provenance OK: $pkg" + + - name: Install workspace (frozen lockfile) + run: bun install --frozen-lockfile + + # Integrity-idempotent publish. ORDER (all in one step so nothing runs between the TOCTOU + # re-check and `npm publish`): + # 1. `npm pack` ONCE; capture the tarball's integrity. + # 2. Classify the registry FIRST via `npm view`: a confirmed 404 authorizes publish; the + # version already present with a MATCHING integrity is an idempotent skip-success; a + # DIFFERENT integrity is a hard fail (an immutable version must never differ); any other + # error (401/403/429/5xx/network) fails TRANSIENTLY (safe to re-dispatch the same tag). + # 3. On the publish path only, re-run the SHARED resolve-tag.sh IMMEDIATELY before publish + # and fail closed if the tag no longer peels to the pinned SHA (last-instant TOCTOU). + # 4. `npm publish `, then a bounded >=5-min read-back (v0.1.0: ~3-min propagation). + - name: Integrity-idempotent OIDC publish + env: + PKG: "@ceralive/modem-control" + VERSION: ${{ needs.tag-guard.outputs.version }} + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + RELEASE_TAG: ${{ github.event.inputs.tag }} + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + # Provenance attestations only when the source repo is public. + NPM_CONFIG_PROVENANCE: ${{ !github.event.repository.private }} + run: | + set -euo pipefail + + # (1) pack exactly once; the integrity of THIS tarball is what the registry must carry. + npm pack ./control --json > pack.json + TARBALL="$(node -p "require('./pack.json')[0].filename")" + LOCAL_INTEGRITY="$(node -p "require('./pack.json')[0].integrity")" + [ -n "$LOCAL_INTEGRITY" ] || { echo "::error::could not read local pack integrity"; exit 1; } + echo "packed ${TARBALL} (integrity ${LOCAL_INTEGRITY})" + + # Registry probe -> MISSING | PRESENT: | TRANSIENT:. + registry_state() { + local err out rc + err="$(mktemp)" + if out="$(npm view "${PKG}@${VERSION}" dist.integrity 2>"$err")"; then rc=0; else rc=$?; fi + if [ "$rc" -eq 0 ] && [ -n "$out" ]; then + printf 'PRESENT:%s' "$out" + elif grep -qiE 'E404|404 Not Found' "$err"; then + printf 'MISSING' + else + printf 'TRANSIENT:%s' "$(tr '\n' ' ' < "$err" | cut -c1-160)" + fi + rm -f "$err" + } + + # (2) classify FIRST. Only a confirmed MISSING (404) authorizes the publish branch. + decision="" + for attempt in 1 2 3; do + state="$(registry_state)" + case "$state" in + PRESENT:*) + reg="${state#PRESENT:}" + if [ "$reg" = "$LOCAL_INTEGRITY" ]; then + echo "idempotent skip: ${PKG}@${VERSION} already published, integrity matches" + decision="skip" + else + echo "::error::${PKG}@${VERSION} exists with DIFFERENT integrity (registry ${reg} != local ${LOCAL_INTEGRITY}); refusing" + exit 1 + fi + break + ;; + MISSING) + decision="publish"; break ;; + TRANSIENT:*) + echo "transient registry error (attempt ${attempt}/3): ${state#TRANSIENT:}" + sleep 15 ;; + esac + done + if [ -z "$decision" ]; then + echo "::error::registry classification unresolved (transient); re-dispatch the same tag is safe" + exit 1 + fi + if [ "$decision" = "skip" ]; then + echo "publish-npm: nothing to do (idempotent skip-success)" + exit 0 + fi + + # (3) last-instant TOCTOU guard — nothing runs between this and `npm publish`. + sha_now="$(bash packaging/ci/resolve-tag.sh "$REPO_URL" "$RELEASE_TAG")" + if [ "$sha_now" != "$EXPECTED_SHA" ]; then + echo "::error::tag ${RELEASE_TAG} moved before publish: now ${sha_now}, expected ${EXPECTED_SHA}" + exit 1 + fi + npm publish "$TARBALL" --access public + + # (4) bounded >=5-min read-back until the registry reports the matching integrity. + deadline=$(( $(date +%s) + 300 )) + while :; do + state="$(registry_state)" + case "$state" in + PRESENT:*) + reg="${state#PRESENT:}" + if [ "$reg" = "$LOCAL_INTEGRITY" ]; then + echo "read-back OK: ${PKG}@${VERSION} integrity confirmed on the registry" + break + fi + echo "::error::read-back integrity mismatch (registry ${reg} != local ${LOCAL_INTEGRITY})" + exit 1 + ;; + *) + if [ "$(date +%s)" -ge "$deadline" ]; then + echo "::error::publish did not propagate within 5 min (${state}); re-dispatch is safe (it will skip)" + exit 1 + fi + echo "waiting for registry propagation: ${state}" + sleep 20 ;; + esac + done + + create-release: + name: Create GitHub release (staged sanitized assets, immutable reconcile) + needs: [tag-guard, build-deb, publish-npm] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout the resolved commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.tag-guard.outputs.sha }} + + - name: Assert checkout is pinned to the resolved SHA + env: + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + run: | + head="$(git rev-parse HEAD)" + if [ "$head" != "$EXPECTED_SHA" ]; then + echo "::error::HEAD ${head} != resolved ${EXPECTED_SHA}" + exit 1 + fi + echo "HEAD pinned to ${head}" + + # A moved tag between dispatch and here means someone force-moved it — HARD FAIL. + - name: Pre-create tag re-verification (moved tag = HARD FAIL) + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + RELEASE_TAG: ${{ github.event.inputs.tag }} + EXPECTED_SHA: ${{ needs.tag-guard.outputs.sha }} + run: | + sha_now="$(bash packaging/ci/resolve-tag.sh "$REPO_URL" "$RELEASE_TAG")" + if [ "$sha_now" != "$EXPECTED_SHA" ]; then + echo "::error::tag ${RELEASE_TAG} moved: now ${sha_now}, expected ${EXPECTED_SHA}" + exit 1 + fi + echo "Tag still peels to ${sha_now}" + + - name: Download built .deb + manifest artifact + uses: actions/download-artifact@v8 + with: + name: modem-stack-debs-${{ needs.tag-guard.outputs.version }} + path: artifact + + # Assemble a FLAT dir of the raw assets (every deb + the manifest). Sanitization, + # collision detection, immutable reconciliation, and the final manifest-complete verify + # all live in the shared reconcile-release-assets.sh (never inlined here). + - name: Assemble flat assets dir (all debs + manifest) + run: | + mkdir -p staged-assets + find artifact -name '*.deb' -type f -exec cp {} staged-assets/ \; + manifest="$(find artifact -name release-manifest.txt -type f | head -n1)" + if [ -z "$manifest" ]; then + echo "::error::release-manifest.txt not present in the downloaded artifact" + exit 1 + fi + cp "$manifest" staged-assets/ + echo "assembled $(find staged-assets -type f | wc -l) asset(s)" + + - name: Reconcile release assets (immutable, manifest-complete) + env: + RELEASE_TAG: ${{ github.event.inputs.tag }} + GH_TOKEN: ${{ github.token }} + run: bash packaging/ci/reconcile-release-assets.sh "$RELEASE_TAG" staged-assets diff --git a/packaging/README.md b/packaging/README.md index 23c3479..f9ecbb8 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -91,4 +91,6 @@ ModemManager 1.24.2, libmbim 1.34.0, libqmi 1.38.0, libqrtr-glib 1.4.0 (salsa | [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (bookworm container) entry point. Lightweight, needs no built `.deb`: asserts the scaffold, the tag-guard contract, that `dch` version-injection runs on a **copy** (the committed changelogs stay pristine), and the real `dpkg --compare-versions` tilde ordering. The deb-consuming contract lives in the two scripts below. | | [`ci/test-package-contract.sh`](ci/test-package-contract.sh) | The **package contract suite** over the A5.1 build output. `test-package-contract.sh ` launches a `debian:bookworm` container and runs: metadata/arch over the 9-package closure (revision-exact — every deb's base must equal its `read-pin.sh` `-`); clean-bookworm `apt-get install ./*.deb`; upgrade (stock 1.20.4 → ceralive set) with a **direction-aware** `--allow-downgrades` (computed per-package from real `dpkg --compare-versions` vs `madison` stock — post-bump every source sorts ABOVE stock, so the flag is dropped); rollback (`madison`-derived stock versions + `--allow-downgrades`); coherence (identical `~ceralive` suffix + mismatched-libqmi negative); real ordering proofs; tag-guard negative; piuparts-style install→purge leftover-scan. All version literals are `read-pin.sh`-derived. amd64 = full; arm64 defaults to `metadata` mode (`CONTRACT_MODE=full` forces the apt scenarios under QEMU). | | [`ci/daemon-smoke.sh`](ci/daemon-smoke.sh) | The **daemon smoke**. `daemon-smoke.sh ` installs system D-Bus + polkit + NetworkManager (bookworm 1.42.4) and the built MM debs, starts a system `dbus-daemon` + `ModemManager`, then asserts: `busctl introspect` shows the root `ObjectManager`; `mmcli --version` matches the **pinned** ModemManager upstream version (via `ci/read-pin.sh`, never hardcoded); the udev-rules + FCC-unlock dispatcher dirs exist; and — **functional GI validation**, not presence-only (it installs `python3-gi valac build-essential pkg-config`) — the `gir1.2-modemmanager-1.0` typelib **loads** through PyGObject (`gi.require_version('ModemManager','1.0')` + a real `ModemManager.ModemCapability.LTE` enum read) and the `libmm-glib` `.vapi` **compiles+links** via `valac -C` → `cc $(pkg-config --cflags --libs mm-glib)` against a Vala program that genuinely calls a libmm-glib symbol (a broken/absent GI-1.74 adaptation fails closed here). amd64 by default. | -| [`ci/generate-release-manifest.sh`](ci/generate-release-manifest.sh) | Emits the **per-release manifest** (`generate-release-manifest.sh ` → `dist/release-manifest.txt`) mapping the release tag to the 9 runtime deb versions **per arch** — the `arch package source version filename sha256` matrix Phase-B apt publication consumes. dpkg-free (filename parse + `sha256sum`), so it runs anywhere. | +| [`ci/generate-release-manifest.sh`](ci/generate-release-manifest.sh) | Emits the **manifest-complete per-release manifest** (`generate-release-manifest.sh ` → `dist/release-manifest.txt`): a checksum row for **every** built deb (both arches), the 9-package runtime closure MARKED (`role=runtime`, the rest `role=aux`) — the `arch package source version role filename sha256` matrix Phase-B apt publication AND `create-release` asset reconciliation consume. Fails closed if the produced set (per source, per arch) is not exactly the frozen `[ all-artifact]` set in [`ci/expected-packages.txt`](ci/expected-packages.txt). dpkg-free (filename parse + `sha256sum`), so it runs anywhere. | +| [`ci/resolve-tag.sh`](ci/resolve-tag.sh) | The **shared tag → peeled-commit-SHA resolver** used by `release.yml`. `resolve-tag.sh ` asks the remote (`git ls-remote`, no clone) for both `refs/tags/` and `refs/tags/^{}`, prefers the **peeled** commit SHA (an annotated tag otherwise resolves to its tag object), prints it, and fails closed if the tag is absent or ambiguous. ONE script, called from tag-guard (pin every checkout), publish-npm (last-instant pre-publish TOCTOU re-check), and create-release (pre-create re-check) — no divergent copies. A caller detects a moved tag by comparing the output against the pinned SHA. | +| [`ci/reconcile-release-assets.sh`](ci/reconcile-release-assets.sh) | The **immutable, manifest-complete release-asset reconciler** used by `release.yml`'s `create-release`. `reconcile-release-assets.sh ` takes a flat dir of the raw built debs + the manifest and: verifies the deb set equals the manifest sha256-exactly (missing/extra/corrupt ⇒ fail closed); stages each asset under its **own sanitized basename** (`~` → `.`, never relying on GitHub's upload mapping) and rejects any name **collision**; creates the release if absent; then for each staged asset uploads it if MISSING or integrity-compares (download + sha256) if it already EXISTS — matching ⇒ skip (idempotent), differing ⇒ fail closed (published assets are never overwritten); and finally verifies the live asset set equals the staged set. `RECONCILE_RELEASE_DIR=` selects a local mock backend for standalone testing. | diff --git a/packaging/ci/generate-release-manifest.sh b/packaging/ci/generate-release-manifest.sh index 5013e67..1704bb8 100755 --- a/packaging/ci/generate-release-manifest.sh +++ b/packaging/ci/generate-release-manifest.sh @@ -2,21 +2,25 @@ # generate-release-manifest.sh [build-root] [out-file] — emit the per-release manifest # that maps ONE release tag to the exact .deb versions it produced, per arch. # -# Phase-B apt publication consumes this file as the package -> source -> version matrix, so -# it is the single source of truth for "which debs are release ". It is deliberately -# dpkg-free: it parses each `__.deb` filename and sha256sums the -# file, so it runs identically on a CI runner, a bench box, or this dev host. +# Phase-B apt publication AND the create-release asset reconciler both consume this file as the +# single source of truth for "which debs are release ", so it is MANIFEST-COMPLETE: it +# lists a checksum row for EVERY built deb (both arches) — the 9-package runtime closure is a +# MARKED subset (role=runtime) and the -dev/gir/-dbgsym debs are role=aux. It is deliberately +# dpkg-free: it parses each `__.deb` filename and sha256sums the file, +# so it runs identically on a CI runner, a bench box, or this dev host. +# +# It also FAILS CLOSED if the produced set is not exactly the frozen all-artifact set: per arch, +# per source, the enumerated packages must EQUAL `[ all-artifact]` in expected-packages.txt +# (the two-set model finalized in todo 1.4). An added, dropped, renamed, or unmapped deb is a +# hard error — the manifest can never silently under- or over-report the release contents. # # INPUT build-root (default: packaging/build) holding /*.deb from build-bookworm.sh. +# EXPECTED_PACKAGES (env, default: alongside this script) — the frozen package sets. # OUTPUT a manifest at out-file (default: dist/release-manifest.txt), also echoed to stdout. # -# The manifest lists the 9-package RUNTIME closure per arch (the "9 exact deb versions" the -# plan speaks of). Non-runtime debs (-dev / -dbgsym / gir1.2-*) are recorded in a trailing -# comment count but are not part of the runtime version matrix. -# # USAGE generate-release-manifest.sh v0.1.0 # generate-release-manifest.sh v0.1.0 packaging/build dist/release-manifest.txt -# EXIT 0 ok. 2 usage / no debs. +# EXIT 0 ok. 2 usage / no debs / unreadable expected-packages. 3 package-set inequality. set -euo pipefail TAG="${1:-}" @@ -26,20 +30,17 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PKG_ROOT="$(cd "$HERE/.." && pwd)" BUILD_ROOT="${2:-$PKG_ROOT/build}" OUT="${3:-$PKG_ROOT/../dist/release-manifest.txt}" +EXPECTED="${EXPECTED_PACKAGES:-$HERE/expected-packages.txt}" +[ -r "$EXPECTED" ] || { echo "generate-release-manifest: cannot read expected-packages '$EXPECTED'" >&2; exit 2; } # Strip a leading v for the encoded suffix (tag guard already vetted the shape upstream). VERSION="${TAG#v}" SUFFIX="~ceralive${VERSION}" -# The 9-package runtime closure and each package's source (for the Phase-B matrix). -declare -A SOURCE_OF=( - [modemmanager]=ModemManager [libmm-glib0]=ModemManager - [libmbim-glib4]=libmbim [libmbim-proxy]=libmbim [libmbim-utils]=libmbim - [libqmi-glib5]=libqmi [libqmi-proxy]=libqmi [libqmi-utils]=libqmi - [libqrtr-glib0]=libqrtr-glib -) +# The 9-package runtime closure — used to mark rows (role) and to assert none is missing. RUNTIME_PKGS=(modemmanager libmm-glib0 libmbim-glib4 libmbim-proxy libmbim-utils \ libqmi-glib5 libqmi-proxy libqmi-utils libqrtr-glib0) +is_runtime() { local p="$1" r; for r in "${RUNTIME_PKGS[@]}"; do [ "$r" = "$p" ] && return 0; done; return 1; } # Parse "__.deb" -> pkg / version / arch (version never contains '_'). deb_field() { # @@ -49,50 +50,106 @@ deb_field() { # case "$2" in pkg) echo "$pkg" ;; version) echo "$ver" ;; arch) echo "$arch" ;; esac } +# ---- expected-packages.txt readers (the [ all-artifact] blocks) -------------------- +expected_sources() { + awk -F'[][]' '/^\[[^]]+ all-artifact\]/ { split($2, a, " "); print a[1] }' "$EXPECTED" | LC_ALL=C sort -u +} +expected_set() { # -> its all-artifact package list, sorted-unique + awk -v want="[$1 all-artifact]" ' + /^\[/ { h=$0; sub(/[ \t]*#.*$/, "", h); insec=(h==want)?1:0; next } + insec { l=$0; sub(/#.*$/, "", l); gsub(/[ \t]+/, "", l); if (l!="") print l } + ' "$EXPECTED" | LC_ALL=C sort -u +} + +# Reverse map: package -> owning source (across every all-artifact block). +declare -A SOURCE_OF=() +mapfile -t SOURCES < <(expected_sources) +for src in "${SOURCES[@]}"; do + while IFS= read -r pkg; do + [ -n "$pkg" ] && SOURCE_OF["$pkg"]="$src" + done < <(expected_set "$src") +done + mkdir -p "$(dirname "$OUT")" { echo "# CeraLive modem-stack release manifest" - echo "# Maps release tag -> exact .deb versions (Phase-B apt publication consumes this)." + echo "# Maps release tag -> exact .deb versions (Phase-B apt + create-release consume this)." + echo "# MANIFEST-COMPLETE: one row per built deb, both arches; runtime closure marked." echo "tag: ${TAG}" echo "version: ${VERSION}" echo "deb_version_suffix: ${SUFFIX}" - echo "sources: [ModemManager, libmbim, libqmi, libqrtr-glib]" + echo "sources: [${SOURCES[*]}]" echo "runtime_closure_size: ${#RUNTIME_PKGS[@]}" - echo "# columns: arch package source version filename sha256" + echo "# columns: arch package source version role filename sha256" } > "$OUT" +total_all=0 total_runtime=0 arches_seen=() +rc=0 for archdir in "$BUILD_ROOT"/*/; do [ -d "$archdir" ] || continue arch="$(basename "$archdir")" ls "$archdir"/*.deb >/dev/null 2>&1 || continue arches_seen+=("$arch") - # Index this arch's debs by package name. + # Index this arch's debs by package name, and collect the produced package set. declare -A FILE_OF=() for deb in "$archdir"/*.deb; do FILE_OF["$(deb_field "$(basename "$deb")" pkg)"]="$deb" done + produced_sorted="$(printf '%s\n' "${!FILE_OF[@]}" | LC_ALL=C sort -u)" + # (a) No runtime package may be absent (kept from the original closure check). for pkg in "${RUNTIME_PKGS[@]}"; do - deb="${FILE_OF[$pkg]:-}" - [ -n "$deb" ] || { echo "generate-release-manifest: MISSING runtime deb '$pkg' for $arch" >&2; exit 2; } + [ -n "${FILE_OF[$pkg]:-}" ] || { echo "generate-release-manifest: MISSING runtime deb '$pkg' for $arch" >&2; exit 2; } + done + + # (b) Every produced package must map to a known source (else it is unexpected). + while IFS= read -r p; do + [ -n "$p" ] || continue + [ -n "${SOURCE_OF[$p]:-}" ] || { echo "generate-release-manifest: FAIL CLOSED — [$arch] produced package '$p' is in no expected all-artifact set" >&2; rc=3; } + done <<< "$produced_sorted" + + # (c) Per-source EQUALITY: produced set for this source == its all-artifact set. + for src in "${SOURCES[@]}"; do + want="$(expected_set "$src")" + # `|| true`: the filter's last test can be a non-match (exit 1); pipefail would else + # abort. An empty `got` simply fails the equality below, closed. + got="$(printf '%s\n' "$produced_sorted" | while IFS= read -r p; do [ "${SOURCE_OF[$p]:-}" = "$src" ] && printf '%s\n' "$p"; done | LC_ALL=C sort -u)" || true + if [ "$want" != "$got" ]; then + echo "generate-release-manifest: FAIL CLOSED — [$src/$arch] set != expected all-artifact:" >&2 + comm -23 <(printf '%s\n' "$want") <(printf '%s\n' "$got") | sed 's/^/ MISSING: /' >&2 || true + comm -13 <(printf '%s\n' "$want") <(printf '%s\n' "$got") | sed 's/^/ UNEXPECTED: /' >&2 || true + rc=3 + fi + done + + # (d) Emit a row for EVERY deb (sorted by package for a deterministic manifest). + while IFS= read -r pkg; do + [ -n "$pkg" ] || continue + deb="${FILE_OF[$pkg]}" fn="$(basename "$deb")" ver="$(deb_field "$fn" version)" sha="$(sha256sum "$deb" | awk '{print $1}')" - printf '%s %s %s %s %s %s\n' "$arch" "$pkg" "${SOURCE_OF[$pkg]}" "$ver" "$fn" "$sha" >> "$OUT" - total_runtime=$((total_runtime + 1)) - done + if is_runtime "$pkg"; then role=runtime; total_runtime=$((total_runtime + 1)); else role=aux; fi + printf '%s %s %s %s %s %s %s\n' "$arch" "$pkg" "${SOURCE_OF[$pkg]:-UNKNOWN}" "$ver" "$role" "$fn" "$sha" >> "$OUT" + total_all=$((total_all + 1)) + done <<< "$produced_sorted" + unset FILE_OF done { echo "# arches: ${arches_seen[*]:-none}" - echo "# runtime_debs_total: ${total_runtime} (= ${#RUNTIME_PKGS[@]} runtime x ${#arches_seen[@]} arch)" + echo "# all_debs_total: ${total_all} runtime_debs_total: ${total_runtime} (across ${#arches_seen[@]} arch)" } >> "$OUT" -[ "$total_runtime" -gt 0 ] || { echo "generate-release-manifest: no runtime debs found under $BUILD_ROOT" >&2; exit 2; } +[ "$total_all" -gt 0 ] || { echo "generate-release-manifest: no debs found under $BUILD_ROOT" >&2; exit 2; } +if [ "$rc" -ne 0 ]; then + echo "generate-release-manifest: STOP — produced package set != frozen all-artifact sets." >&2 + exit "$rc" +fi cat "$OUT" -echo "generate-release-manifest: wrote $OUT (${total_runtime} runtime deb rows across ${#arches_seen[@]} arch)" >&2 +echo "generate-release-manifest: wrote $OUT (${total_all} deb rows, ${total_runtime} runtime, across ${#arches_seen[@]} arch)" >&2 diff --git a/packaging/ci/reconcile-release-assets.sh b/packaging/ci/reconcile-release-assets.sh new file mode 100755 index 0000000..3b7e38f --- /dev/null +++ b/packaging/ci/reconcile-release-assets.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +# reconcile-release-assets.sh — IMMUTABLE, manifest-complete release-asset +# reconciliation for the ModemManager stack release. +# +# WHAT IT DOES (one shared helper — the release.yml create-release job AND the QA drills both +# call THIS script; there is deliberately NO inline copy of this logic in the workflow YAML): +# +# 1. MANIFEST-COMPLETENESS. is a FLAT directory holding the release's raw +# artifacts: every built `.deb` (both arches, with their `~ceralive` build names) plus the +# `release-manifest.txt`. The manifest is the completeness oracle: the set of `.deb` files +# present MUST equal, byte-for-byte by sha256, the set the manifest enumerates — no more, +# no less. A missing, extra, or corrupted deb FAILS CLOSED naming it. +# +# 2. SANITIZED STAGING. GitHub Release asset upload historically rewrites `~` -> `.` in an +# asset name. We NEVER rely on that mapping: every asset is staged as a FLAT copy under its +# OWN sanitized basename (`~` -> `.`), computed here. Two distinct sources that sanitize to +# the SAME name are a hard COLLISION -> FAIL CLOSED (they would otherwise silently overwrite). +# +# 3. IMMUTABLE RECONCILE. The release is created if absent. Then, for each staged asset: +# * MISSING on the release -> upload it (plain `gh release upload`, never overwriting). +# * EXISTS on the release -> download the live copy into a fresh dir and sha256-compare +# it to the staged file. EQUAL -> skip (idempotent re-run). +# DIFFERENT -> FAIL CLOSED (published assets are immutable; +# we NEVER overwrite one). +# Overwriting a published asset is structurally impossible here — there is no overwrite flag +# used anywhere in this script, by design. +# +# 4. FINAL VERIFY. The live asset set (names + count) must EQUAL the staged set. Anything else +# FAILS CLOSED. +# +# BACKEND (testable without a live GitHub release): +# Default — talks to GitHub via `gh` (release view/create/upload/download). +# RECONCILE_RELEASE_DIR= — LOCAL MOCK: that directory stands in for the release's asset +# store (its files are the "existing" assets). This is how the QA drills +# exercise the missing / integrity-match / integrity-mismatch / collision +# paths against scratch files. The directory not existing == release absent. +# +# Usage: reconcile-release-assets.sh +# Exit: +# 0 reconciled; live asset set == staged set (manifest-complete). +# 2 usage / unreadable input / manifest missing. +# 3 FAIL CLOSED: completeness gap, name collision, integrity mismatch, or verify mismatch. +set -euo pipefail + +TAG="${1:-}" +ASSETS_DIR="${2:-}" +MANIFEST_NAME="${RECONCILE_MANIFEST_NAME:-release-manifest.txt}" + +[ -n "$TAG" ] && [ -n "$ASSETS_DIR" ] || { + echo "usage: reconcile-release-assets.sh " >&2 + exit 2 +} +[ -d "$ASSETS_DIR" ] || { echo "reconcile: assets dir '$ASSETS_DIR' does not exist" >&2; exit 2; } + +MANIFEST="$ASSETS_DIR/$MANIFEST_NAME" +[ -r "$MANIFEST" ] || { echo "reconcile: manifest '$MANIFEST' not found in assets dir" >&2; exit 2; } + +VERSION="${TAG#v}" +sha_of() { sha256sum "$1" | awk '{print $1}'; } + +# ---- backend selection -------------------------------------------------------------------- +gh_mode() { [ -z "${RECONCILE_RELEASE_DIR:-}" ]; } + +release_exists() { + if gh_mode; then gh release view "$TAG" >/dev/null 2>&1 + else [ -d "$RECONCILE_RELEASE_DIR" ]; fi +} +create_release() { # + if gh_mode; then + gh release create "$TAG" --title "$TAG" --notes-file "$1" + else + mkdir -p "$RECONCILE_RELEASE_DIR" + fi +} +list_live_assets() { # -> asset basenames, one per line, sorted + if gh_mode; then + gh release view "$TAG" --json assets --jq '.assets[].name' | LC_ALL=C sort + else + find "$RECONCILE_RELEASE_DIR" -maxdepth 1 -type f -printf '%f\n' 2>/dev/null | LC_ALL=C sort + fi +} +fetch_existing_asset() { # + if gh_mode; then + gh release download "$TAG" --pattern "$1" --dir "$2" >/dev/null + else + cp "$RECONCILE_RELEASE_DIR/$1" "$2/$1" + fi +} +upload_asset() { # (plain upload; overwriting is never requested) + if gh_mode; then + gh release upload "$TAG" "$1" + else + cp "$1" "$RECONCILE_RELEASE_DIR/" + fi +} + +# ---- 0. enumerate the assets dir; enforce it holds ONLY debs + the manifest ---------------- +mapfile -t ALL_FILES < <(find "$ASSETS_DIR" -maxdepth 1 -type f -printf '%f\n' | LC_ALL=C sort) +deb_files=() +for f in "${ALL_FILES[@]}"; do + case "$f" in + "$MANIFEST_NAME") ;; + *.deb) deb_files+=("$f") ;; + *) + echo "reconcile: FAIL CLOSED — unexpected asset '$f' (only *.deb + $MANIFEST_NAME allowed)" >&2 + exit 3 + ;; + esac +done +[ "${#deb_files[@]}" -gt 0 ] || { echo "reconcile: no *.deb files in $ASSETS_DIR" >&2; exit 2; } + +# ---- 1. manifest-completeness: assets-dir debs == manifest debs, sha256-exact -------------- +# Each manifest deb row carries a `.deb` token and a 64-hex sha256 token (column order is +# not assumed). Emit "\t" per deb row. +manifest_debs() { + awk ' + /^[[:space:]]*#/ { next } + { + fn=""; sh="" + for (i=1; i<=NF; i++) { + if ($i ~ /\.deb$/) fn=$i + else if ($i ~ /^[0-9a-f]{64}$/) sh=$i + } + if (fn != "" && sh != "") print fn "\t" sh + } + ' "$MANIFEST" | LC_ALL=C sort +} + +fail=0 +declare -A MANIFEST_SHA=() +manifest_names_tmp="$(mktemp)" +while IFS=$'\t' read -r fn sh; do + [ -n "$fn" ] || continue + MANIFEST_SHA["$fn"]="$sh" + printf '%s\n' "$fn" >>"$manifest_names_tmp" +done < <(manifest_debs) +manifest_names="$(LC_ALL=C sort "$manifest_names_tmp")"; rm -f "$manifest_names_tmp" +[ -n "$manifest_names" ] || { echo "reconcile: manifest lists no deb rows — cannot verify completeness" >&2; exit 2; } + +present_names="$(printf '%s\n' "${deb_files[@]}" | LC_ALL=C sort)" + +# Debs the manifest expects but the assets dir does not have (the drill-(i) case). +missing="$(comm -23 <(printf '%s\n' "$manifest_names") <(printf '%s\n' "$present_names") || true)" +# Debs present in the assets dir that the manifest does not list. +extra="$(comm -13 <(printf '%s\n' "$manifest_names") <(printf '%s\n' "$present_names") || true)" + +if [ -n "$missing" ]; then + while IFS= read -r m; do [ -n "$m" ] && echo "reconcile: FAIL CLOSED — manifest deb missing from assets: $m" >&2; done <<<"$missing" + fail=1 +fi +if [ -n "$extra" ]; then + while IFS= read -r e; do [ -n "$e" ] && echo "reconcile: FAIL CLOSED — asset deb not in manifest: $e" >&2; done <<<"$extra" + fail=1 +fi +[ "$fail" -eq 0 ] || exit 3 + +# sha256 of every deb must match the manifest (a corrupt/rebuilt deb is caught here). +for fn in "${deb_files[@]}"; do + want="${MANIFEST_SHA[$fn]}" + got="$(sha_of "$ASSETS_DIR/$fn")" + if [ "$want" != "$got" ]; then + echo "reconcile: FAIL CLOSED — deb '$fn' sha256 $got != manifest $want" >&2 + fail=1 + fi +done +[ "$fail" -eq 0 ] || exit 3 +echo "reconcile: manifest-complete — ${#deb_files[@]} deb(s) present, sha256-exact vs $MANIFEST_NAME" + +# ---- 2. sanitized staging (~ -> .) + collision detection ----------------------------------- +STAGE_DIR="$(mktemp -d)" +DL_DIR="$(mktemp -d)" +trap 'rm -rf "$STAGE_DIR" "$DL_DIR"' EXIT + +declare -A STAGED_FROM=() +staged_names=() +for src in "${ALL_FILES[@]}"; do + target="${src//\~/.}" + if [ -n "${STAGED_FROM[$target]:-}" ]; then + echo "reconcile: FAIL CLOSED — sanitized-name collision '$target' from both '${STAGED_FROM[$target]}' and '$src'" >&2 + exit 3 + fi + STAGED_FROM["$target"]="$src" + cp "$ASSETS_DIR/$src" "$STAGE_DIR/$target" + staged_names+=("$target") +done +staged_sorted="$(printf '%s\n' "${staged_names[@]}" | LC_ALL=C sort)" +echo "reconcile: staged ${#staged_names[@]} asset(s) under sanitized names" + +# ---- 3. ensure the release exists, then reconcile immutably -------------------------------- +if release_exists; then + echo "reconcile: release $TAG exists — reconciling against its current assets" +else + notes="$(mktemp)" + { + echo "CeraLive modem-stack $TAG" + echo + echo "- npm: https://www.npmjs.com/package/@ceralive/modem-control/v/${VERSION}" + echo "- debs: ${#deb_files[@]} package(s), both arches (see ${MANIFEST_NAME})" + echo "- manifest: ${MANIFEST_NAME} (tag -> per-arch .deb versions + sha256)" + echo "- asset names are sanitized (\`~\` -> \`.\`) from their build filenames." + } >"$notes" + echo "reconcile: release $TAG absent — creating it" + create_release "$notes" + rm -f "$notes" +fi + +live_before="$(list_live_assets || true)" +for name in "${staged_names[@]}"; do + if printf '%s\n' "$live_before" | grep -qxF "$name"; then + # EXISTING asset — integrity-compare, never overwrite. + rm -rf "${DL_DIR:?}"/* + if ! fetch_existing_asset "$name" "$DL_DIR"; then + echo "reconcile: FAIL CLOSED — could not fetch existing asset '$name' for integrity compare" >&2 + exit 3 + fi + live_sha="$(sha_of "$DL_DIR/$name")" + staged_sha="$(sha_of "$STAGE_DIR/$name")" + if [ "$live_sha" = "$staged_sha" ]; then + echo "reconcile: SKIP $name (already published, integrity matches: $staged_sha)" + else + echo "reconcile: FAIL CLOSED — published asset '$name' differs (live $live_sha != staged $staged_sha); refusing to overwrite" >&2 + exit 3 + fi + else + echo "reconcile: UPLOAD $name (missing from release)" + upload_asset "$STAGE_DIR/$name" + fi +done + +# ---- 4. final verify: live asset set == staged set ----------------------------------------- +live_after="$(list_live_assets || true)" +if [ "$live_after" != "$staged_sorted" ]; then + echo "reconcile: FAIL CLOSED — final asset set != staged set:" >&2 + comm -23 <(printf '%s\n' "$staged_sorted") <(printf '%s\n' "$live_after") \ + | sed 's/^/ MISSING from release: /' >&2 || true + comm -13 <(printf '%s\n' "$staged_sorted") <(printf '%s\n' "$live_after") \ + | sed 's/^/ UNEXPECTED on release: /' >&2 || true + exit 3 +fi + +echo "reconcile: OK — release $TAG carries exactly ${#staged_names[@]} asset(s), manifest-complete." diff --git a/packaging/ci/resolve-tag.sh b/packaging/ci/resolve-tag.sh new file mode 100755 index 0000000..7b4ffd6 --- /dev/null +++ b/packaging/ci/resolve-tag.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# resolve-tag.sh — resolve a release tag to its immutable peeled commit SHA. +# +# The release workflow pins EVERY checkout to the commit a tag points at, never to the mutable +# tag ref itself. This is the single source of that resolution: given a repository URL and a +# tag name, it asks the remote (`git ls-remote`, no clone) for BOTH the tag ref and its peeled +# form `^{}`, PREFERS the peeled COMMIT sha (an annotated tag otherwise resolves to its +# tag OBJECT, not the commit), and FAILS CLOSED when the tag is absent or resolves ambiguously. +# +# It has FOUR call sites — the workflow's tag-guard resolve step, publish-npm's pre-publish +# TOCTOU re-check, create-release's pre-create re-check, and the QA drills — so the resolution +# logic lives HERE ONCE, never as a divergent copy inlined in the YAML. +# +# The caller detects a MOVED tag by comparing this script's output against a previously-resolved +# sha (e.g. `[ "$(resolve-tag.sh "$url" "$tag")" = "$PINNED" ]`); the script itself only ever +# reports what the tag currently peels to. +# +# Usage: resolve-tag.sh +# stdout : the resolved 40-hex commit sha (exit 0). +# stderr : a reason, on failure. +# Exit: +# 0 resolved sha printed. +# 1 tag absent / ambiguous / malformed remote output / ls-remote failed. +# 2 usage. +set -euo pipefail + +REPO_URL="${1:-}" +TAG="${2:-}" + +if [ -z "$REPO_URL" ] || [ -z "$TAG" ]; then + echo "resolve-tag: usage: resolve-tag.sh " >&2 + exit 2 +fi + +# One network round-trip: ask for the tag ref AND its peeled form together. Exact refspecs +# (no globbing) so the remote can only answer with these two ref names. +if ! refs="$(git ls-remote "$REPO_URL" "refs/tags/$TAG" "refs/tags/$TAG^{}")"; then + echo "resolve-tag: git ls-remote failed for '$REPO_URL'" >&2 + exit 1 +fi + +if [ -z "$refs" ]; then + echo "resolve-tag: tag '$TAG' not found on '$REPO_URL'" >&2 + exit 1 +fi + +plain_sha="" +peeled_sha="" +while IFS=$'\t' read -r sha ref; do + [ -n "$sha" ] || continue + case "$ref" in + "refs/tags/$TAG^{}") peeled_sha="$sha" ;; + "refs/tags/$TAG") plain_sha="$sha" ;; + *) + # Exact refspecs were requested; anything else is an ambiguous/hostile remote answer. + echo "resolve-tag: unexpected ref '$ref' in ls-remote output for tag '$TAG'" >&2 + exit 1 + ;; + esac +done <<<"$refs" + +# Prefer the peeled commit (annotated tag); fall back to the plain ref (lightweight tag, which +# already points straight at a commit). +resolved="${peeled_sha:-$plain_sha}" + +if [ -z "$resolved" ]; then + echo "resolve-tag: tag '$TAG' resolved to no sha on '$REPO_URL'" >&2 + exit 1 +fi + +# Defensive: ls-remote always emits 40-hex object names; anything else is corrupt output. +if ! [[ "$resolved" =~ ^[0-9a-f]{40}$ ]]; then + echo "resolve-tag: tag '$TAG' resolved to a non-sha value '$resolved'" >&2 + exit 1 +fi + +printf '%s\n' "$resolved" From da0e37fcc2d1a4d1730dd2cdee7e08474d41494e Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 02:04:44 -0500 Subject: [PATCH 11/14] refactor(transport): split transport.ts into lifecycle/calls/signals modules (behavior-locked) --- control/src/transport/calls.ts | 113 +++++++++ control/src/transport/no-library-leak.test.ts | 15 +- control/src/transport/signals.ts | 150 +++++++++++ control/src/transport/transport.ts | 237 ++---------------- 4 files changed, 300 insertions(+), 215 deletions(-) create mode 100644 control/src/transport/calls.ts create mode 100644 control/src/transport/signals.ts diff --git a/control/src/transport/calls.ts b/control/src/transport/calls.ts new file mode 100644 index 0000000..7059a44 --- /dev/null +++ b/control/src/transport/calls.ts @@ -0,0 +1,113 @@ +// Method-call dispatch and reply correlation for the D-Bus transport seam. +// +// A call marshals its body through the codec, registers a pending record so a bus drop can +// reject it, and installs a per-call reply callback guarded by a `done` flag — so a reply +// that arrives after the call already timed out or the connection dropped is silently +// ignored rather than mis-correlated onto a stale promise. `rejectAll` mass-rejects every +// in-flight call when the connection drops. + +import { decodeBody, encodeBody } from './codec'; +import { messageType, type RawBus, type RawMessage, type ReplyContext } from './dbus-native'; +import { DisconnectedError, TransportError } from './errors'; +import type { DbusValue, MethodCall, MethodReply } from './types'; + +export const DEFAULT_CALL_TIMEOUT_MS = 30_000; + +interface PendingCall { + settle(): void; + reject(error: unknown): void; +} + +export class CallDispatcher { + readonly #pending = new Set(); + readonly #callTimeoutMs: number; + + constructor(callTimeoutMs: number) { + this.#callTimeoutMs = callTimeoutMs; + } + + async call(bus: RawBus | null, connected: boolean, call: MethodCall): Promise { + if (!connected || bus === null) { + throw new DisconnectedError('cannot call method: transport not connected'); + } + + const signature = call.signature ?? ''; + const args = call.args ?? []; + const message: RawMessage = { + type: messageType.methodCall, + destination: call.destination, + path: call.path, + interface: call.interface, + member: call.member, + }; + if (signature.length > 0) { + // Throws UnsupportedSignatureError / BigIntRequiredError before anything hits + // the wire. + message.signature = signature; + message.body = encodeBody(signature, args); + } + + const timeoutMs = call.timeoutMs ?? this.#callTimeoutMs; + const pendingSet = this.#pending; + return new Promise((resolve, reject) => { + let done = false; + const pending: PendingCall = { + settle: finish, + reject: (error) => { + finish(); + reject(error); + }, + }; + + function finish(): void { + if (done) { + return; + } + done = true; + clearTimeout(timer); + pendingSet.delete(pending); + } + + const timer = setTimeout(() => { + finish(); + reject( + new TransportError( + `Method call ${call.interface}.${call.member} timed out after ${timeoutMs}ms`, + ), + ); + }, timeoutMs); + + pendingSet.add(pending); + + bus.invoke( + message, + function reply(this: ReplyContext, error: unknown, ...body: unknown[]): void { + if (done) { + // Reply arrived after timeout/disconnect already settled the promise — ignore. + return; + } + finish(); + if (error) { + reject(error instanceof Error ? error : new TransportError(String(error))); + return; + } + try { + const replySignature = this.signature ?? ''; + const decoded: DbusValue[] = + replySignature.length > 0 ? decodeBody(replySignature, body) : []; + resolve({ signature: replySignature, body: decoded }); + } catch (decodeError) { + reject(decodeError); + } + }, + ); + }); + } + + rejectAll(cause: unknown): void { + for (const pending of this.#pending) { + pending.reject(cause); + } + this.#pending.clear(); + } +} diff --git a/control/src/transport/no-library-leak.test.ts b/control/src/transport/no-library-leak.test.ts index 3a6804c..d9f6300 100644 --- a/control/src/transport/no-library-leak.test.ts +++ b/control/src/transport/no-library-leak.test.ts @@ -6,6 +6,7 @@ // fallback `@particle/dbus-next`) must stay invisible to every caller. import { expect, test } from 'bun:test'; +import { readdirSync } from 'node:fs'; import { join } from 'node:path'; import * as transportPublic from './index'; @@ -26,11 +27,21 @@ test('the transport public entry does not import or re-export the D-Bus library' }); test('only the quarantined facade imports the D-Bus library from production modules', async () => { - const productionModules = ['transport.ts', 'codec.ts', 'signature.ts', 'errors.ts', 'types.ts']; + // Enumerate every non-test production module in transport/ dynamically, so a NEW module + // (e.g. a future transport split) that imports the library directly is caught — the old + // fixed list never knew about files it did not name. The importer set must be EXACTLY the + // sanctioned facade, `dbus-native.ts`. + const productionModules = readdirSync(transportDir).filter( + (name) => name.endsWith('.ts') && !name.endsWith('.test.ts'), + ); + const importers: string[] = []; for (const moduleName of productionModules) { const source = await Bun.file(join(transportDir, moduleName)).text(); - expect(source).not.toContain(LIBRARY_IMPORT); + if (source.includes(LIBRARY_IMPORT)) { + importers.push(moduleName); + } } + expect(importers.sort()).toEqual(['dbus-native.ts']); }); test('the transport public surface exposes only the seam\u2019s own values', () => { diff --git a/control/src/transport/signals.ts b/control/src/transport/signals.ts new file mode 100644 index 0000000..b932a7a --- /dev/null +++ b/control/src/transport/signals.ts @@ -0,0 +1,150 @@ +// Signal subscription and match-rule tracking for the D-Bus transport seam. +// +// The transport's single persistent `message` listener fans out here: `dispatch` walks the +// live subscription registry and delivers each decoded signal to every matching listener. +// Match rules are refcounted so N subscriptions sharing a rule add/remove it on the bus +// exactly once, and `reissueRules` re-adds every live rule after a reconnect — so +// subscribing/unsubscribing never grows the connection's listener count (the 100-cycle +// leak check depends on this). + +import { decodeBody } from './codec'; +import { messageType, type RawBus, type RawMessage } from './dbus-native'; +import type { DbusValue, SignalEvent, SignalListener, SignalSpec, Subscription } from './types'; + +// The live connection context the registry reads through. The transport supplies these so +// the registry always sees the current bus/connected state (which change across reconnects) +// rather than capturing a stale reference, and routes decode/listener failures to the +// transport's `error` event. +export interface SignalHost { + currentBus(): RawBus | null; + isConnected(): boolean; + emitError(error: unknown): void; +} + +interface SubscriptionRecord { + readonly id: number; + readonly spec: SignalSpec; + readonly listener: SignalListener; + readonly rule: string; +} + +function buildMatchRule(spec: SignalSpec): string { + const parts = [`type='signal'`, `interface='${spec.interface}'`, `member='${spec.member}'`]; + if (spec.path !== undefined) { + parts.push(`path='${spec.path}'`); + } + if (spec.sender !== undefined) { + parts.push(`sender='${spec.sender}'`); + } + return parts.join(','); +} + +function signalMatches(spec: SignalSpec, message: RawMessage): boolean { + if (message.interface !== spec.interface || message.member !== spec.member) { + return false; + } + if (spec.path !== undefined && message.path !== spec.path) { + return false; + } + if (spec.sender !== undefined && message.sender !== spec.sender) { + return false; + } + return true; +} + +export class SignalRegistry { + readonly #host: SignalHost; + readonly #subscriptions = new Map(); + readonly #matchRuleRefcount = new Map(); + #nextSubId = 1; + + constructor(host: SignalHost) { + this.#host = host; + } + + async subscribe(spec: SignalSpec, listener: SignalListener): Promise { + const rule = buildMatchRule(spec); + const id = this.#nextSubId++; + this.#subscriptions.set(id, { id, spec, listener, rule }); + await this.#addMatchRule(rule); + + let removed = false; + return { + unsubscribe: async (): Promise => { + if (removed) { + return; + } + removed = true; + this.#subscriptions.delete(id); + await this.#removeMatchRule(rule); + }, + }; + } + + count(): number { + return this.#subscriptions.size; + } + + // Re-issue every live match rule against a freshly established bus so a reconnect + // resubscribes transparently. Called by `#establish` before it swaps in the new bus, so + // the fresh bus is passed in explicitly rather than read from the host. + async reissueRules(bus: RawBus): Promise { + for (const rule of this.#matchRuleRefcount.keys()) { + await bus.addMatch(rule); + } + } + + dispatch(message: RawMessage): void { + if (message.type !== messageType.signal) { + return; + } + for (const record of this.#subscriptions.values()) { + if (!signalMatches(record.spec, message)) { + continue; + } + let body: DbusValue[]; + try { + const signature = message.signature ?? ''; + body = signature.length > 0 ? decodeBody(signature, message.body ?? []) : []; + } catch (error) { + this.#host.emitError(error); + continue; + } + const event: SignalEvent = { + path: message.path ?? '', + interface: message.interface ?? '', + member: message.member ?? '', + sender: message.sender, + signature: message.signature ?? '', + body, + }; + try { + record.listener(event); + } catch (error) { + this.#host.emitError(error); + } + } + } + + async #addMatchRule(rule: string): Promise { + const current = this.#matchRuleRefcount.get(rule) ?? 0; + this.#matchRuleRefcount.set(rule, current + 1); + const bus = this.#host.currentBus(); + if (current === 0 && this.#host.isConnected() && bus) { + await bus.addMatch(rule); + } + } + + async #removeMatchRule(rule: string): Promise { + const current = this.#matchRuleRefcount.get(rule) ?? 0; + if (current <= 1) { + this.#matchRuleRefcount.delete(rule); + const bus = this.#host.currentBus(); + if (current === 1 && this.#host.isConnected() && bus) { + await bus.removeMatch(rule).catch(() => undefined); + } + } else { + this.#matchRuleRefcount.set(rule, current - 1); + } + } +} diff --git a/control/src/transport/transport.ts b/control/src/transport/transport.ts index d0151ce..099dadf 100644 --- a/control/src/transport/transport.ts +++ b/control/src/transport/transport.ts @@ -1,29 +1,26 @@ // The D-Bus transport seam implementation. // -// Wraps `@httptoolkit/dbus-native` behind the `DbusTransport` interface: method calls, -// signal subscriptions, and an automatic reconnect loop that re-issues every match rule -// after a bus restart. A single persistent `message` listener fans out to the live -// subscription registry, so subscribing/unsubscribing never grows the connection's -// listener count — the 100-cycle leak check depends on this. +// Wraps `@httptoolkit/dbus-native` behind the `DbusTransport` interface. This module owns the +// connection lifecycle — handshake, a reconnect loop that re-issues every match rule after a +// bus restart, and teardown — and delegates method-call dispatch to `./calls` and signal +// subscription/match-rule tracking to `./signals`. Its single `message` listener fans out to +// the signal registry, so subscribing never grows the listener count (the 100-cycle leak check). import { EventEmitter } from 'node:events'; -import { decodeBody, encodeBody } from './codec'; +import { CallDispatcher, DEFAULT_CALL_TIMEOUT_MS } from './calls'; import { type CreateClientOptions, createClient, - messageType, type RawBus, type RawMessage, - type ReplyContext, } from './dbus-native'; import { DisconnectedError, TransportError } from './errors'; +import { SignalRegistry } from './signals'; import type { DbusTransport, DbusTransportOptions, - DbusValue, MethodCall, MethodReply, - SignalEvent, SignalListener, SignalSpec, Subscription, @@ -39,19 +36,6 @@ interface ResolvedReconnect { readonly maxAttempts: number; } -interface SubscriptionRecord { - readonly id: number; - readonly spec: SignalSpec; - readonly listener: SignalListener; - readonly rule: string; -} - -interface PendingCall { - settle(): void; - reject(error: unknown): void; -} - -const DEFAULT_CALL_TIMEOUT_MS = 30_000; // Bound a single connect/auth attempt so a stalled handshake cannot freeze the reconnect // loop. A local unix-socket D-Bus connect completes in milliseconds; 2s is ample headroom // while keeping reconnect responsive after a bus restart. @@ -65,46 +49,19 @@ const DEFAULT_RECONNECT: ResolvedReconnect = { const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); -function buildMatchRule(spec: SignalSpec): string { - const parts = [`type='signal'`, `interface='${spec.interface}'`, `member='${spec.member}'`]; - if (spec.path !== undefined) { - parts.push(`path='${spec.path}'`); - } - if (spec.sender !== undefined) { - parts.push(`sender='${spec.sender}'`); - } - return parts.join(','); -} - -function signalMatches(spec: SignalSpec, message: RawMessage): boolean { - if (message.interface !== spec.interface || message.member !== spec.member) { - return false; - } - if (spec.path !== undefined && message.path !== spec.path) { - return false; - } - if (spec.sender !== undefined && message.sender !== spec.sender) { - return false; - } - return true; -} - class DbusTransportImpl implements DbusTransport { readonly #options: DbusTransportOptions; readonly #reconnect: ResolvedReconnect; - readonly #callTimeoutMs: number; readonly #emitter = new EventEmitter(); - readonly #subscriptions = new Map(); - readonly #matchRuleRefcount = new Map(); - readonly #pending = new Set(); + readonly #calls: CallDispatcher; + readonly #signals: SignalRegistry; #bus: RawBus | null = null; #state: State = 'idle'; #closing = false; - #nextSubId = 1; // Bound once so the same references can be detached from a dead connection. - readonly #onMessage = (message: RawMessage): void => this.#dispatchSignal(message); + readonly #onMessage = (message: RawMessage): void => this.#signals.dispatch(message); readonly #onConnectionError = (cause: unknown): void => this.#handleDrop(cause instanceof Error ? cause : new DisconnectedError(String(cause))); readonly #onConnectionEnd = (): void => @@ -112,7 +69,12 @@ class DbusTransportImpl implements DbusTransport { constructor(options: DbusTransportOptions) { this.#options = options; - this.#callTimeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS; + this.#calls = new CallDispatcher(options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS); + this.#signals = new SignalRegistry({ + currentBus: () => this.#bus, + isConnected: () => this.#state === 'connected', + emitError: (error) => this.#emitter.emit('error', error), + }); this.#reconnect = { enabled: options.reconnect?.enabled ?? DEFAULT_RECONNECT.enabled, initialDelayMs: options.reconnect?.initialDelayMs ?? DEFAULT_RECONNECT.initialDelayMs, @@ -144,113 +106,23 @@ class DbusTransportImpl implements DbusTransport { this.#state = 'closed'; const bus = this.#bus; this.#bus = null; - this.#rejectPending(new DisconnectedError('transport closed')); + this.#calls.rejectAll(new DisconnectedError('transport closed')); if (bus) { this.#quiesce(bus); await bus.disconnect().catch(() => undefined); } } - async callMethod(call: MethodCall): Promise { - const bus = this.#bus; - if (this.#state !== 'connected' || bus === null) { - throw new DisconnectedError('cannot call method: transport not connected'); - } - - const signature = call.signature ?? ''; - const args = call.args ?? []; - const message: RawMessage = { - type: messageType.methodCall, - destination: call.destination, - path: call.path, - interface: call.interface, - member: call.member, - }; - if (signature.length > 0) { - // Throws UnsupportedSignatureError / BigIntRequiredError before anything hits - // the wire. - message.signature = signature; - message.body = encodeBody(signature, args); - } - - const timeoutMs = call.timeoutMs ?? this.#callTimeoutMs; - const pendingSet = this.#pending; - return new Promise((resolve, reject) => { - let done = false; - const pending: PendingCall = { - settle: finish, - reject: (error) => { - finish(); - reject(error); - }, - }; - - function finish(): void { - if (done) { - return; - } - done = true; - clearTimeout(timer); - pendingSet.delete(pending); - } - - const timer = setTimeout(() => { - finish(); - reject( - new TransportError( - `Method call ${call.interface}.${call.member} timed out after ${timeoutMs}ms`, - ), - ); - }, timeoutMs); - - pendingSet.add(pending); - - bus.invoke( - message, - function reply(this: ReplyContext, error: unknown, ...body: unknown[]): void { - if (done) { - // Reply arrived after timeout/disconnect already settled the promise — ignore. - return; - } - finish(); - if (error) { - reject(error instanceof Error ? error : new TransportError(String(error))); - return; - } - try { - const replySignature = this.signature ?? ''; - const decoded: DbusValue[] = - replySignature.length > 0 ? decodeBody(replySignature, body) : []; - resolve({ signature: replySignature, body: decoded }); - } catch (decodeError) { - reject(decodeError); - } - }, - ); - }); + callMethod(call: MethodCall): Promise { + return this.#calls.call(this.#bus, this.#state === 'connected', call); } - async subscribeSignal(spec: SignalSpec, listener: SignalListener): Promise { - const rule = buildMatchRule(spec); - const id = this.#nextSubId++; - this.#subscriptions.set(id, { id, spec, listener, rule }); - await this.#addMatchRule(rule); - - let removed = false; - return { - unsubscribe: async (): Promise => { - if (removed) { - return; - } - removed = true; - this.#subscriptions.delete(id); - await this.#removeMatchRule(rule); - }, - }; + subscribeSignal(spec: SignalSpec, listener: SignalListener): Promise { + return this.#signals.subscribe(spec, listener); } subscriptionCount(): number { - return this.#subscriptions.size; + return this.#signals.count(); } on(event: TransportEvent, handler: (payload?: unknown) => void): void { @@ -300,9 +172,7 @@ class DbusTransportImpl implements DbusTransport { bus.connection.on('end', this.#onConnectionEnd); // Re-issue every live match rule so a reconnect resubscribes transparently. - for (const rule of this.#matchRuleRefcount.keys()) { - await bus.addMatch(rule); - } + await this.#signals.reissueRules(bus); this.#bus = bus; this.#state = 'connected'; @@ -319,58 +189,6 @@ class DbusTransportImpl implements DbusTransport { } } - #dispatchSignal(message: RawMessage): void { - if (message.type !== messageType.signal) { - return; - } - for (const record of this.#subscriptions.values()) { - if (!signalMatches(record.spec, message)) { - continue; - } - let body: DbusValue[]; - try { - const signature = message.signature ?? ''; - body = signature.length > 0 ? decodeBody(signature, message.body ?? []) : []; - } catch (error) { - this.#emitter.emit('error', error); - continue; - } - const event: SignalEvent = { - path: message.path ?? '', - interface: message.interface ?? '', - member: message.member ?? '', - sender: message.sender, - signature: message.signature ?? '', - body, - }; - try { - record.listener(event); - } catch (error) { - this.#emitter.emit('error', error); - } - } - } - - async #addMatchRule(rule: string): Promise { - const current = this.#matchRuleRefcount.get(rule) ?? 0; - this.#matchRuleRefcount.set(rule, current + 1); - if (current === 0 && this.#state === 'connected' && this.#bus) { - await this.#bus.addMatch(rule); - } - } - - async #removeMatchRule(rule: string): Promise { - const current = this.#matchRuleRefcount.get(rule) ?? 0; - if (current <= 1) { - this.#matchRuleRefcount.delete(rule); - if (current === 1 && this.#state === 'connected' && this.#bus) { - await this.#bus.removeMatch(rule).catch(() => undefined); - } - } else { - this.#matchRuleRefcount.set(rule, current - 1); - } - } - #detachHandlers(bus: RawBus): void { bus.connection.removeListener('message', this.#onMessage as (...args: unknown[]) => void); bus.connection.removeListener('error', this.#onConnectionError); @@ -385,13 +203,6 @@ class DbusTransportImpl implements DbusTransport { bus.connection.on('error', () => undefined); } - #rejectPending(cause: unknown): void { - for (const pending of this.#pending) { - pending.reject(cause); - } - this.#pending.clear(); - } - #handleDrop(cause: unknown): void { if (this.#closing) { return; @@ -404,7 +215,7 @@ class DbusTransportImpl implements DbusTransport { this.#quiesce(this.#bus); } this.#bus = null; - this.#rejectPending(cause); + this.#calls.rejectAll(cause); this.#emitter.emit('disconnected', cause); if (this.#reconnect.enabled) { void this.#reconnectLoop(); From b12a5acc1344156cfa3b0d977433e51b4596d8d6 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 02:12:17 -0500 Subject: [PATCH 12/14] chore(biome): sync schema URL to 2.5.4 --- biome.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biome.json b/biome.json index 0b71703..035ceef 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.4/schema.json", "extends": ["@ceralive/biome-config"], "files": { "includes": ["**", "!**/node_modules", "!**/dist", "!test-results"] From 1d3b805bacb85916c86bf6351262a7ba73aa8396 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 02:29:24 -0500 Subject: [PATCH 13/14] docs: version truth sweep for the 1.24.2/1.34.0/1.38.0/1.4.0 stack --- AGENTS.md | 67 +++++++++++++++++++++++++----- README.md | 6 +-- docs/BENCH.md | 50 ++++++++++++---------- docs/VERSIONING.md | 26 ++++++------ packaging/ci/inject-deb-version.sh | 2 +- 5 files changed, 103 insertions(+), 48 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b35e044..c07df19 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,11 +44,43 @@ npm and the `.deb` set. `.deb` versions encode the tag as `-~cera (upstream-ordered, apt-safe; injected with `dch --force-bad-version`). Non-tag CI builds use `~ceralive0.0.0~dev`. Full contract: `docs/VERSIONING.md`. +## PROVENANCE PINS (packaging) + +The four rebuilt sources are pinned in `packaging/upstream-pins.yaml`, re-verified end-to-end +by `packaging/ci/verify-upstream-pins.sh` in an isolated `GNUPGHOME`. Current pins: + +| Source | Upstream | Salsa packaging tag | +|--------|----------|---------------------| +| ModemManager | 1.24.2 | `debian/1.24.2-2` | +| libmbim | 1.34.0 | `debian/1.34.0-1` | +| libqmi | 1.38.0 | `debian/1.38.0-1` | +| libqrtr-glib | 1.4.0 | `debian/1.4.0-1` | + +Each pin carries a **four-link provenance chain**, all re-checked and failing closed with a +named field on any drift: + +1. **Lineage** — the upstream git tag object + peeled commit SHA (`git ls-remote`; the tag is + never byte-compared to a git archive). +2. **Authority** — the signed Debian `.dsc`, GPG-verified against a pinned signer fingerprint + whose armored key lives in `packaging/keys/` (mapping in `packaging/keys/README.md`). +3. **Artifact** — the `.orig.tar`, whose sha256 equals the verified `.dsc`'s + `Checksums-Sha256` entry. +4. **Packaging** — the `.debian.tar.xz`, whose sha256 equals the `.dsc`, and whose extracted + `debian/` tree is proven byte-identical to the pinned salsa tag via a canonical metadata + manifest (path, file type, exec-bit, symlink target, content sha256 per entry). + +The container build additionally enforces the finalized **two-set package model** (declared +arch-dependent stanzas + enumerated `-dbgsym`) for exact per-source set **equality** via +`packaging/ci/check-package-sets.sh` (add/remove/rename fails closed). Full detail: +`packaging/README.md`. + ## POLICY `packaging/` is a **no-fork** effort: the first release carries zero quilt patches; adding a patch later is an architecture gate (rationale + filed upstream MR + review); udev/plugin/device-support improvements go **upstream first**. Full terms: `POLICY.md`. +The Fibocom **FM350** modem (PCIe / `mtk_t7xx`) is documented-**deferred**, not supported — +rationale, source cites, and the open gates are recorded in `docs/FM350-DECISION.md`. ## WORKSPACE / TOOLCHAIN @@ -90,18 +122,33 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor closure / upgrade / rollback / daemon smoke) lands in a later task. `cancel-in-progress: true`. - **`.github/workflows/release.yml`** — the **single** release workflow, owns **both** - artifacts. `workflow_dispatch` with a `tag` input. Job graph: - 1. **tag-guard** (`packaging/ci/tag-guard.sh`) — input must match `^v\d+\.\d+\.\d+$`; - anything else (pre-release, build metadata, missing `v`) **fails closed** before any - other job runs. + artifacts. `workflow_dispatch` with a `tag` input. **Strictly sequential** job graph + `tag-guard → test → build-deb → publish-npm → create-release` (build-before-publish; npm + never publishes before the `.deb` set builds green). Every downstream job checks out + `ref: needs.tag-guard.outputs.sha` and re-asserts `git rev-parse HEAD` equals that peeled + SHA; every dynamic input is routed through `env:` (no `${{ }}` in any `run:` body): + 1. **tag-guard** — resolves the tag to its **peeled commit SHA** + (`packaging/ci/resolve-tag.sh`, `git ls-remote`, prefers `refs/tags/^{}`), + re-checks out that SHA, asserts HEAD, then runs `packaging/ci/tag-guard.sh` (input must + match `^v\d+\.\d+\.\d+$`; pre-release / build-metadata / missing-`v` **fails closed** + before any other job). Exports `version` + `sha`. 2. **test** (needs tag-guard) — full bun lane + packaging contract lane (test-before-publish). - 3. **publish-npm** (needs test) — OIDC trusted publishing (`id-token: write`), verifies - `control/package.json` version === tag, `npm publish --access public`. - 4. **build-deb** (needs test) — strips `v`, injects `-~ceralive` - into each source's `debian/changelog` via `dch --force-bad-version` - (`packaging/ci/inject-deb-version.sh`), uploads `.deb` artifacts + a release manifest. - Non-tag runs use `~ceralive0.0.0~dev`. + 3. **build-deb** (needs [tag-guard, test]) — injects `-~ceralive` + (non-tag runs `~ceralive0.0.0~dev`) via `packaging/ci/inject-deb-version.sh`, builds both + arches, runs the package contract suite + daemon smoke, generates the manifest-complete + release manifest (`packaging/ci/generate-release-manifest.sh`), and uploads the `.deb` + artifacts + manifest. + 4. **publish-npm** (needs [tag-guard, build-deb]) — OIDC trusted publishing + (`id-token: write`), verifies `control/package.json` version === tag, then an + **integrity-idempotent** publish: `npm pack` → classify registry state (404 → publish; + present+matching integrity → idempotent skip; present+differing → fail closed), with a + last-instant `resolve-tag.sh` re-verification immediately before `npm publish`. + 5. **create-release** (needs [tag-guard, build-deb, publish-npm], `contents: write`) — + pre-create moved-tag re-check, downloads the `.deb` + manifest artifact, assembles a flat + asset dir, and reconciles it immutably via `packaging/ci/reconcile-release-assets.sh` + (manifest-complete, staged sanitized `~`→`.` names, collision-rejected, existing assets + integrity-compared and never overwritten). `cancel-in-progress: false` (never cancel a release/publish mid-run). Action pins track the latest stable **major** (resolved via the `gh api` releases/latest diff --git a/README.md b/README.md index 8c3a564..5f104db 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ changes to CeraUI, the device image, or apt-worker**. Bench devices install the ## Versioning at a glance -ONE unified **SemVer** tag `vX.Y.Z` releases **both** artifacts together: `v0.1.0` publishes -`@ceralive/modem-control@0.1.0` to npm **and** the `.deb` artifact set in the same release. +ONE unified **SemVer** tag `vX.Y.Z` releases **both** artifacts together: `v0.2.0` publishes +`@ceralive/modem-control@0.2.0` to npm **and** the `.deb` artifact set in the same release. This repo deliberately does **not** use the CeraLive CalVer scheme. The `.deb` internal `Version:` fields encode the tag as `-~ceralive` (e.g. -`1.24.0-1~ceralive0.1.0`) so apt ordering stays correct. Full contract: +`1.24.2-2~ceralive0.2.0`) so apt ordering stays correct. Full contract: [`docs/VERSIONING.md`](docs/VERSIONING.md). ## Layout diff --git a/docs/BENCH.md b/docs/BENCH.md index 39a338b..6bb97ac 100644 --- a/docs/BENCH.md +++ b/docs/BENCH.md @@ -186,7 +186,7 @@ b=$(sed 's/.*: *//' A6.3/slot-uid-before.txt); a=$(sed 's/.*: *//' A6.3/slot-uid ## RB-3 — MM-1.24-from-artifacts install `[PARTIAL]` Install the packaged ModemManager 1.24 stack on a clean bench device **from the release CI -artifacts** (Phase A does no apt publication) and prove the daemon comes up at 1.24.0. This +artifacts** (Phase A does no apt publication) and prove the daemon comes up at 1.24.2. This is the on-hardware counterpart of A5.2's daemon smoke (which runs in a `debian:bookworm` container in CI). @@ -199,25 +199,28 @@ container in CI). ```sh mkdir -p A6.3 debs && cd debs -# Preferred: download the permanent v0.1.0 GitHub Release assets (durable — never expires): -gh release download v0.1.0 --repo CERALIVE/modem-stack --dir . --clobber +# Preferred: download the permanent latest-release GitHub Release assets (durable — never +# expires); v0.2.0 is the latest release at time of writing: +gh release download v0.2.0 --repo CERALIVE/modem-stack --dir . --clobber # -> release-manifest.txt + all 54 .deb files, flat, arch encoded in the filename suffix # (..._amd64.deb / ..._arm64.deb). # # NOTE: GitHub Release upload sanitizes `~` to `.` in asset filenames (e.g. -# modemmanager_1.24.0-1~ceralive0.1.0_amd64.deb -> modemmanager_1.24.0-1.ceralive0.1.0_amd64.deb). +# modemmanager_1.24.2-2~ceralive0.2.0_amd64.deb -> modemmanager_1.24.2-2.ceralive0.2.0_amd64.deb). # This is cosmetic only — package content, embedded Debian version, and installability are # unaffected (verified byte-identical via sha256 against release-manifest.txt). apt/dpkg read # the version from the package's internal control data, not the filename. # -# Fallback (time-limited — CI workflow-run artifact retention expires 2026-10-13, ~90 days -# from the release run): `gh run download --repo CERALIVE/modem-stack -n modem-stack-debs-0.1.0 +# Fallback (time-limited — CI workflow-run artifact retention expires ~90 days after the +# release run): `gh run download --repo CERALIVE/modem-stack -n modem-stack-debs-0.2.0 # --dir .` — this variant nests debs under packaging/build//*.deb instead of a flat dir; # adjust the install glob below accordingly if you use this path instead of the Release. ARCH=$(dpkg --print-architecture) # arm64 on the shipping SBC sudo apt-get update -sudo apt-get install -y --allow-downgrades ./*_"$ARCH".deb | tee ../A6.3/mm-install.txt +# Post-bump every source outranks bookworm stock (see the note below), so installing the +# CeraLive set over stock is a pure upgrade — no `--allow-downgrades` needed: +sudo apt-get install -y ./*_"$ARCH".deb | tee ../A6.3/mm-install.txt cd .. # Bring the daemon up (or `systemctl restart ModemManager` on a systemd device): @@ -230,23 +233,28 @@ busctl introspect org.freedesktop.ModemManager1 /org/freedesktop/ModemManager1 \ **Expected output** ``` -mmcli 1.24.0 +mmcli 1.24.2 ``` -and the coherence check: every installed stack package carries the same `~ceralive0.1.0` +and the coherence check: every installed stack package carries the same `~ceralive0.2.0` suffix (per the release manifest). **Machine check** ```sh -grep -q 'mmcli 1\.24\.0' A6.3/mm-version.txt \ - && awk -F'[ \t]+' 'NR>0 && /~ceralive0\.1\.0/{n++} END{exit !(n>=9)}' debs/release-manifest.txt \ +grep -q 'mmcli 1\.24\.2' A6.3/mm-version.txt \ + && awk -F'[ \t]+' 'NR>0 && /~ceralive0\.2\.0/{n++} END{exit !(n>=9)}' debs/release-manifest.txt \ && echo "RB-3 PASS" || echo "RB-3 FAIL" ``` -> `libqrtr-glib0` is the one package whose `~ceralive0.1.0` sorts **below** bookworm stock -> `1.2.2-1` — hence `--allow-downgrades` on install. Phase-B image integration replaces this -> with apt pin 990. All other three sources sort above stock. +> **Direction (post-bump):** all four sources now sort **above** bookworm stock — +> ModemManager `1.24.2-2` > `1.20.4-1`, libmbim `1.34.0-1` > `1.28.2-1`, libqmi `1.38.0-1` > +> `1.32.2-1`, and — newly — libqrtr-glib `1.4.0-1` > `1.2.2-1`. libqrtr-glib **flipped** from +> below to above at this bump: pre-bump its `1.2.2-1~ceralive…` was tilde-lower than stock +> `1.2.2-1` and needed `--allow-downgrades`; the new `1.4.0-1` outranks stock outright. So the +> install above is a pure **upgrade** and needs **no** `--allow-downgrades` — only the reverse +> (rollback to stock) is a downgrade. Phase-B image integration replaces the artifact install +> with apt pin 990. (Empirically confirmed in `test-results/upstream-currency/1.3/`.) **Evidence:** `test-results/modem-control/A6.3/{mm-install,mm-version}.txt` @@ -460,7 +468,7 @@ mmcli --version | tee -a A6.3/arm64-probe.txt ``` aarch64 PROBE OK: external-auth, objects= -mmcli 1.24.0 +mmcli 1.24.2 ``` **Machine check** @@ -468,7 +476,7 @@ mmcli 1.24.0 ```sh grep -q aarch64 A6.3/arm64-uname.txt \ && grep -Eq '^PROBE OK: external-auth, objects=[1-9]' A6.3/arm64-probe.txt \ - && grep -q 'mmcli 1\.24\.0' A6.3/arm64-probe.txt \ + && grep -q 'mmcli 1\.24\.2' A6.3/arm64-probe.txt \ && echo "RB-7 PASS" || echo "RB-7 FAIL" ``` @@ -498,22 +506,22 @@ mkdir -p A6.3 } | tee A6.3/daemon-smoke.txt ``` -**Expected output** — MM owns the bus name, reports version `1.24.0`, and the udev rules are +**Expected output** — MM owns the bus name, reports version `1.24.2`, and the udev rules are present: ``` org.freedesktop.ModemManager1 … -.Version property s "1.24.0" … +.Version property s "1.24.2" … /usr/lib/udev/rules.d/77-mm-…rules -mmcli 1.24.0 +mmcli 1.24.2 ``` **Machine check** ```sh grep -q 'ModemManager1' A6.3/daemon-smoke.txt \ - && grep -q '"1\.24\.0"' A6.3/daemon-smoke.txt \ - && grep -q 'mmcli 1\.24\.0' A6.3/daemon-smoke.txt \ + && grep -q '"1\.24\.2"' A6.3/daemon-smoke.txt \ + && grep -q 'mmcli 1\.24\.2' A6.3/daemon-smoke.txt \ && echo "RB-8 PASS" || echo "RB-8 FAIL" ``` diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index 14169eb..8a2fb51 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -41,40 +41,40 @@ encoding is: -~ceralive ``` -For the first packaging rev (`-1`) of each source at repo tag `v0.1.0`: +For the current pins at repo tag `v0.2.0` (the latest release — the ModemManager revision +is `-2`, the other three `-1`): | Source | Upstream | Encoded `.deb` version | |--------|----------|------------------------| -| ModemManager | 1.24.0 | `1.24.0-1~ceralive0.1.0` | -| libmbim | 1.32.0 | `1.32.0-1~ceralive0.1.0` | -| libqmi | 1.36.0 | `1.36.0-1~ceralive0.1.0` | -| libqrtr-glib | 1.2.2 | `1.2.2-1~ceralive0.1.0` | +| ModemManager | 1.24.2 | `1.24.2-2~ceralive0.2.0` | +| libmbim | 1.34.0 | `1.34.0-1~ceralive0.2.0` | +| libqmi | 1.38.0 | `1.38.0-1~ceralive0.2.0` | +| libqrtr-glib | 1.4.0 | `1.4.0-1~ceralive0.2.0` | -> The upstream versions above are illustrative of the encoding shape. The authoritative, -> provenance-verified upstream pins live in `packaging/upstream-pins.yaml` (added in a -> later task); the release workflow derives `-` from each source's -> `debian/changelog`, never from a value hardcoded in the version script. +> The upstream versions above are the current provenance-verified pins. The authoritative +> manifest is `packaging/upstream-pins.yaml`; the release workflow derives `-` +> from each source's `debian/changelog`, never from a value hardcoded in the version script. ### Why the tilde (`~`) `dpkg` orders a `~` suffix **lower** than the un-suffixed version: ``` -1.24.0-1~ceralive0.1.0 < 1.24.0-1~ceralive0.2.0 < 1.24.0-1 +1.24.2-2~ceralive0.1.0 < 1.24.2-2~ceralive0.2.0 < 1.24.2-2 ``` -So every CeraLive rebuild sorts **below** a hypothetical stock Debian `1.24.0-1`, and a +So every CeraLive rebuild sorts **below** a hypothetical stock Debian `1.24.2-2`, and a newer repo tag (`0.2.0`) sorts **above** an older one (`0.1.0`) — exactly the ordering `apt` needs. This is why the release workflow injects the version with `dch --force-bad-version`: the tilde-encoded version is numerically **lower** than the -pinned `-1` changelog top, and plain `dch --newversion` refuses a +pinned `-` changelog top, and plain `dch --newversion` refuses a lower-than-current version (per `dch(1)`). `--force-bad-version` is **required**, not optional. The exact injection command, run once per source, is: ```sh -dch --force-bad-version --newversion "-1~ceralive" "CeraLive rebuild" +dch --force-bad-version --newversion "-~ceralive" "CeraLive rebuild" ``` All four sources take the **same** `~ceralive` suffix for a given release. diff --git a/packaging/ci/inject-deb-version.sh b/packaging/ci/inject-deb-version.sh index b911f2d..0c83f76 100755 --- a/packaging/ci/inject-deb-version.sh +++ b/packaging/ci/inject-deb-version.sh @@ -61,7 +61,7 @@ for src in "${SOURCES[@]}"; do else # No recipe yet (packaging wave). Document the exact invocation this WILL run. echo " ${src}: no debian/changelog yet — will run:" - echo " dch --force-bad-version --newversion \"-1${SUFFIX}\" \"CeraLive rebuild\"" + echo " dch --force-bad-version --newversion \"-${SUFFIX}\" \"CeraLive rebuild\"" pending=$((pending + 1)) fi done From f01b8e4557bb636ee0ba42ff323dae7b3bcef9f1 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Fri, 17 Jul 2026 02:45:44 -0500 Subject: [PATCH 14/14] test(transport): cancel abandoned fake reply timers on bus drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reconnect/drop test kills the private bus and abandons the fake service without stopping it. A SlowPing whose reply delay had not yet elapsed left a dangling timer that, when it fired, wrote the owed reply to the closed stream and threw "Can't write a message to a closed stream" asynchronously — seconds later, inside an unrelated test file — flipping the run's exit code to 1 while no test actually failed. Track the reply timers and cancel them the instant the connection stream ends (and in stop()), so a dead fake never writes. The live-reply paths are untouched: 'end' only fires when the bus is genuinely killed. --- .../transport/test-support/fake-service.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/control/src/transport/test-support/fake-service.ts b/control/src/transport/test-support/fake-service.ts index e28ade7..8239932 100644 --- a/control/src/transport/test-support/fake-service.ts +++ b/control/src/transport/test-support/fake-service.ts @@ -106,16 +106,40 @@ export async function startFakeService(options: FakeServiceOptions): Promise undefined); + // A reconnect/drop test kills the bus and ABANDONS this fake (stopping it would itself + // write to the closed stream). Any reply the library still owes — e.g. a SlowPing whose + // delay has not elapsed — would then be written to that dead stream when its timer + // fires, throwing "Can't write a message to a closed stream" ASYNCHRONOUSLY, seconds + // later, inside whatever unrelated test happens to be running. Track the reply timers + // and cancel them the instant the connection stream ends, so a dead fake never writes. + const pendingReplyTimers = new Set>(); + const clearPendingReplies = (): void => { + for (const timer of pendingReplyTimers) { + clearTimeout(timer); + } + pendingReplyTimers.clear(); + }; + bus.connection.on('end', clearPendingReplies); + const define = (member: string, impl: MethodImpl, resultSignature: string): void => { bus.setMethodCallHandler(FAKE_PATH, FAKE_IFACE, member, [impl, resultSignature]); }; define('Ping', () => 'pong', 's'); // The library awaits a Promise returned by a handler, so this replies after a delay — - // used to prove a late reply still resolves the caller's method call. + // used to prove a late reply still resolves the caller's method call. The timer is + // tracked so a bus drop before it fires cancels the owed reply instead of writing it + // to a closed stream. define( 'SlowPing', - (delayMs) => new Promise((resolve) => setTimeout(() => resolve('pong'), Number(delayMs))), + (delayMs) => + new Promise((resolve) => { + const timer = setTimeout(() => { + pendingReplyTimers.delete(timer); + resolve('pong'); + }, Number(delayMs)); + pendingReplyTimers.add(timer); + }), 's', ); define('GetManagedObjects', () => managedObjectsValue(), 'a{oa{sa{sv}}}'); @@ -137,6 +161,7 @@ export async function startFakeService(options: FakeServiceOptions): Promise { + clearPendingReplies(); await bus.disconnect().catch(() => undefined); }, };