diff --git a/.github/workflows/ci-packaging.yml b/.github/workflows/ci-packaging.yml index 9917dd7..d469596 100644 --- a/.github/workflows/ci-packaging.yml +++ b/.github/workflows/ci-packaging.yml @@ -1,6 +1,6 @@ name: CI (packaging) -# Paths-filtered PR lane for packaging/ — runs in a bookworm container (the .deb build +# Paths-filtered PR lane for packaging/ — runs in a trixie container (the .deb build # environment). The `push:` trigger carries a positive `branches:` filter alongside # `paths:` so a PR-branch push does not double-fire (CI/CD standard — trigger hygiene). on: @@ -23,9 +23,9 @@ concurrency: jobs: packaging: - name: Packaging contract (bookworm) + name: Packaging contract (trixie) runs-on: ubuntu-latest - container: debian:bookworm + container: debian:trixie steps: # git is needed by actions/checkout; devscripts/dpkg-dev provide dch + # dpkg-parsechangelog for the version-injection path; ccache is the C-build cache @@ -42,9 +42,9 @@ jobs: uses: actions/cache@v6 with: path: ~/.cache/ccache - key: ccache-${{ runner.os }}-bookworm-${{ hashFiles('packaging/**') }} + key: ccache-${{ runner.os }}-trixie-${{ hashFiles('packaging/**') }} restore-keys: | - ccache-${{ runner.os }}-bookworm- + ccache-${{ runner.os }}-trixie- - name: Run packaging contract lane run: bash packaging/ci/contract.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6302582..f13cfea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -156,8 +156,8 @@ jobs: build-deb: name: Build .deb set + contract + daemon smoke (real artifacts) needs: [tag-guard, test] - # Runs on the runner host (NOT inside a container): build-bookworm.sh and the contract / - # smoke scripts each launch their OWN debian:bookworm containers, so the job needs the + # Runs on the runner host (NOT inside a container): build-stack.sh and the contract / + # smoke scripts each launch their OWN debian:trixie containers, so the job needs the # host docker daemon, not to be a container itself. runs-on: ubuntu-latest steps: @@ -256,10 +256,10 @@ jobs: echo "--- verdicts.txt ---" cat verdicts.txt - # STRICTLY BEFORE any build-bookworm.sh invocation. Carried debs are not merely release - # assets, they are a build INPUT: build-bookworm.sh seeds its Pin-Priority-1001 local apt + # STRICTLY BEFORE any build-stack.sh invocation. Carried debs are not merely release + # assets, they are a build INPUT: build-stack.sh seeds its Pin-Priority-1001 local apt # repo from packaging/build//, so a changed source resolves its build-deps and gir - # typelibs against the carried -dev/gir1.2-* packages instead of stock bookworm. Staging + # typelibs against the carried -dev/gir1.2-* packages instead of the stock suite. Staging # after the build would silently reintroduce stock dependencies. - name: Stage carry-forward debs (unchanged sources, sha256-verified) env: @@ -278,7 +278,7 @@ jobs: # all and still asserts the merged runtime closure over the carried set. # # RESIDUAL RISK, ACCEPTED BY THE PLAN: the zero-build path reaching manifest generation with - # staged-only debs is proven by contract fixtures (packaging/ci/test-build-bookworm-differential.sh, + # staged-only debs is proven by contract fixtures (packaging/ci/test-build-stack-differential.sh, # test-suffix-coherence-manifest.sh) and by static wiring proof (test-release-workflow-wiring.sh). # Full end-to-end proof lands at the FIRST REAL RELEASE RUN; it is deliberately not simulated. - name: Build the MM 1.24 stack (.deb) — amd64 + arm64 @@ -287,8 +287,8 @@ jobs: VERDICTS_FILE: verdicts.txt PREV_MANIFEST_FILE: ${{ steps.prev-release.outputs.prev_manifest }} run: | - packaging/ci/build-bookworm.sh amd64 - packaging/ci/build-bookworm.sh arm64 + packaging/ci/build-stack.sh amd64 + packaging/ci/build-stack.sh arm64 # Package contract: amd64 full (metadata / closure install / upgrade / rollback / # coherence / ordering / tag-guard / piuparts); arm64 metadata-level (apt-install under diff --git a/.gitignore b/.gitignore index 4b6af97..2b1e0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ node_modules/ dist/ *.tsbuildinfo -# Packaging build artifacts (.deb output from packaging/ci/build-bookworm.sh) +# Packaging build artifacts (.deb output from packaging/ci/build-stack.sh) packaging/build/ # Local QA evidence (agent-generated, never committed) diff --git a/AGENTS.md b/AGENTS.md index 7c43031..316d237 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,9 +14,10 @@ Canonical branch: `main`. Sole remote: `origin` → `https://github.com/CERALIVE |-----------|----------|------| | `control/` | `@ceralive/modem-control` (npm) | TypeScript control library — domain model, ModemManager D-Bus backend, NetworkManager adapter, desired-state reconciler, injected admission/ownership/USB-hub ports, USB composition-mode model + evidence-bundle **ingestion seam**, data-usage sampler + the **usage-policy write surface**, capability-module **support-claim taxonomy + detection**, and the **band-lock** vocabulary + certification catalog (see §§ below). Published to public npm under `@ceralive` as **built ESM + `.d.ts`** across seven entry points (see § PUBLISHED PACKAGE SURFACE). | | `cli/` | `modem-control` (bench CLI) | The iteration surface: `probe`/`watch`/`apply`/`set-usb-mode`/`usage`/`certify`/`hil-cycle`, compiled `arm64`+`amd64`, run against real modems. Not published to npm. | -| `packaging/` | ModemManager stack `.deb`s **+ the first-party companion** | Bookworm rebuilds of ModemManager + libmbim + libqmi + libqrtr-glib — packaging only, not a fork. libmbim/libqmi/libqrtr-glib remain source-unmodified; ModemManager carries exactly three owner-approved, BELABOX-derived FM350-GL patches, hardware-validated on the carrier-mediated USB topology after restoring the original `ATZ0` first-enable override (see `POLICY.md` and `docs/adr/ADR-FM350-RNDIS-BEARER.md`) — PLUS `ceralive-modem-support`, the `Architecture: all` first-party companion that owns CeraLive's generic modem system assets. | +| `packaging/` | ModemManager stack `.deb`s **+ the first-party companion** | Trixie rebuilds of ModemManager + libmbim + libqmi + libqrtr-glib — packaging only, not a fork. libmbim/libqmi/libqrtr-glib remain source-unmodified; ModemManager carries exactly three owner-approved, BELABOX-derived FM350-GL patches, hardware-validated on the carrier-mediated USB topology after restoring the original `ATZ0` first-enable override (see `POLICY.md` and `docs/adr/ADR-FM350-RNDIS-BEARER.md`) — PLUS `ceralive-modem-support`, the `Architecture: all` first-party companion that owns CeraLive's generic modem system assets. | -`control/` + `cli/` are one **Bun** workspace. `packaging/` builds in a bookworm container. +`control/` + `cli/` are one **Bun** workspace. `packaging/` builds in a **trixie** container — +the suite the device image runs; `build-stack.sh` refuses a container whose suite disagrees. ## FIRST-PARTY COMPANION — `ceralive-modem-support` @@ -1549,7 +1550,7 @@ The **licensing half of the spike did complete** and binds any future adoption: BEFORE any lpac upload. - AGPL §13 attaches to **modified** versions only, so the rule is *ship unmodified or not at all* — the same answer `POLICY.md`'s no-fork rule already gives. -- Shipping form on a hypothetical GO: a first-party bookworm rebuild in `packaging/`, pinned +- Shipping form on a hypothetical GO: a first-party target-suite rebuild in `packaging/`, pinned like the other four sources. The stock Debian package (`lpac` 2.3.0-1) exists but is in testing/unstable only — **not** bookworm, **not** trixie. @@ -1636,17 +1637,32 @@ bun run verify:tarball # pack + assert the published artifact's shape bun run verify:consumers # install the tarball into standalone Node 26 + Bun projects ``` -`packaging/` runs in a `debian:bookworm` container; its contract/verification scripts live +`packaging/` runs in a `debian:$TARGET_SUITE` container (default `trixie`); its contract/verification scripts live under `packaging/ci/`. The four sources' `debian/` recipes are checked in at `packaging//debian/` (`ModemManager`, `libmbim`, `libqmi`, `libqrtr-glib` — matching -their pinned salsa commits except the bookworm adaptations and the approved ModemManager -FM350-GL series documented in `packaging/BOOKWORM-ADAPTATIONS.md`). -`packaging/ci/build-bookworm.sh ` rebuilds +their pinned salsa commits except the suite adaptations and the approved ModemManager +FM350-GL series documented in `packaging/SUITE-ADAPTATIONS.md`). +`packaging/ci/build-stack.sh ` (suite-parameterized: `TARGET_SUITE`, default +`trixie`) rebuilds them from source in the mandatory bootstrap order (`libqrtr-glib → libmbim → libqmi → modemmanager`) via a temporary local apt repo, on native amd64 or full-system-QEMU arm64 (never cross-built), and asserts the 9-package runtime closure. `.deb` output lands in the gitignored `packaging/build//`. +**BUILD-SUITE PARITY IS A GATE.** The build container's own `VERSION_CODENAME` must equal +`TARGET_SUITE` or `build-stack.sh` REFUSES the build. This is not ceremony: a stack built +against a newer libc than the board ships fails at load with a `GLIBC_` symbol error that no +build step can observe, so the mismatch would otherwise surface only on a device. Overriding +`TARGET_SUITE` is for bisecting a suite-specific build break, never for cutting a release. + +The suite move to trixie did NOT let the `debian/` adaptations be reverted, which is the +counter-intuitive part and is re-measured rather than assumed in +`packaging/SUITE-ADAPTATIONS.md`: trixie moved BOTH `udev.pc` and `systemd.pc` into +`systemd-dev`, so the `udev` build-dep supplies neither and the two meson install-dir pins +are *more* load-bearing than they were on bookworm. The GI substitution likewise still +resolves (`libgirepository1.0-dev` is current at `1.84.0-1`), so reverting it would re-open a +verified deviation surface for no gain. + ## CI / CD Follows the CeraLive CI/CD standard (concurrency, trigger hygiene, least privilege, pinned @@ -1659,8 +1675,8 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor `control/scripts/verify-consumers.ts` refuses any major but 26. `cancel-in-progress: true`. - **`.github/workflows/ci-packaging.yml`** — paths-filtered PR + push(`main`) container lane - for `packaging/**`: runs the packaging contract scripts in `debian:bookworm`. The four - `debian/` recipes and `build-bookworm.sh` now exist; the full contract suite (metadata / + for `packaging/**`: runs the packaging contract scripts in `debian:trixie`. The four + `debian/` recipes and `build-stack.sh` now exist; the full contract suite (metadata / closure / upgrade / rollback / daemon smoke) lands in a later task. `cancel-in-progress: true`. - **`.github/workflows/release.yml`** — the **single** release workflow, owns **both** @@ -1686,7 +1702,7 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor the bootstrap case, not an error) → **Detect changed sources (per-source verdicts)** (`packaging/ci/detect-changed-sources.sh --out verdicts.txt`) → **Stage carry-forward debs (unchanged sources, sha256-verified)** (`packaging/ci/stage-carryforward-debs.sh`) → - **Build the MM 1.24 stack (.deb) — amd64 + arm64** (`build-bookworm.sh amd64` /`arm64`, + **Build the MM 1.24 stack (.deb) — amd64 + arm64** (`build-stack.sh amd64` /`arm64`, with `VERDICTS_FILE` + the already-resolved `PREV_MANIFEST_FILE`) → **Package contract suite** (amd64 full, arm64 metadata) → **Daemon smoke (amd64)** → **Build the first-party companion .deb (Architecture: all)** (UNCONDITIONAL — the companion is never detected and @@ -1697,10 +1713,10 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor Three things about that order are load-bearing. The previous release is resolved and its manifest downloaded **exactly once**, and that one path feeds all three consumers (detection, carry-forward staging, per-source counter derivation). Carry-forward staging - runs **strictly before any `build-bookworm.sh` call**, because carried debs are a build - INPUT — `build-bookworm.sh` seeds its Pin-Priority-1001 local apt repo from + runs **strictly before any `build-stack.sh` call**, because carried debs are a build + INPUT — `build-stack.sh` seeds its Pin-Priority-1001 local apt repo from `packaging/build//`, so a changed source resolves its build-deps and gir typelibs - against the carried `-dev`/`gir1.2-*` packages rather than stock bookworm; staging late + against the carried `-dev`/`gir1.2-*` packages rather than the stock suite; staging late still goes green and silently reintroduces stock dependencies, which is why `packaging/ci/test-release-workflow-wiring.sh` pins the ordering statically. And a zero-build run starts no container at all yet still asserts the merged runtime closure @@ -1708,7 +1724,7 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor Detection is **fail-SAFE toward rebuilding**: an absent previous release, a manifest with no `closure_version:` header (an absent header IS closure version 1), a shared-input - change under `packaging/ci/**` or `packaging/BOOKWORM-ADAPTATIONS.md`, or the operator's + change under `packaging/ci/**` or `packaging/SUITE-ADAPTATIONS.md`, or the operator's escape hatch all yield `mode=force-all`. That escape hatch is the `force_rebuild` `workflow_dispatch` boolean input (default `false`), mapped to the script's `FORCE_REBUILD=all` env via diff --git a/POLICY.md b/POLICY.md index 9736879..43cd070 100644 --- a/POLICY.md +++ b/POLICY.md @@ -5,8 +5,9 @@ decisions and require review. ## 1. No-fork gate (packaging) -`packaging/` produces **bookworm rebuilds** of ModemManager, libmbim, libqmi, and -libqrtr-glib. It is a **repackaging** effort, **not a source fork**. +`packaging/` produces **trixie rebuilds** of ModemManager, libmbim, libqmi, and +libqrtr-glib — trixie being the suite the device image runs. It is a **repackaging** effort, +**not a source fork**. - **The first packaging release carries ZERO quilt patches.** Every source is rebuilt from its pinned, provenance-verified upstream release and its pinned Debian packaging diff --git a/README.md b/README.md index 62b4269..f91a205 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ straight from CI artifacts; nothing is published to `apt.ceralive.tv` yet. |-----------|----------|------------| | [`control/`](control/) | **`@ceralive/modem-control`** (npm package) | The TypeScript control library: the [frozen v1.1 domain contracts](docs/DOMAIN-CONTRACTS.md), [provider registry and evidence-scored matcher](docs/PROVIDER-MATCHING.md), concrete typed-D-Bus [`ModemManagerProvider`](docs/MODEMMANAGER-PROVIDER.md) (runtime-discovered generic controls; no CLI subprocess), NetworkManager adapter, desired-state reconciler, injected mutation-admission and exclusive-ownership ports, USB composition-mode model + the [evidence-bundle ingestion seam](docs/CATALOG-INGESTION.md), data-usage sampler plus its `setUsagePolicy` write surface (`control/src/backend/usage/policy-write.ts` — a local 0600 policy file, because ModemManager exposes no data-usage API at all), and the **read-only SMS port** (`control/src/ports/sms.ts` + `control/src/sms/` — LIST/READ plus `Added`/`Deleted` observation, never a send or a delete, locked by `sms/readonly-gate.test.ts`). The USB-hub actuator is port-only here; the bench CLI owns its HIL adapter. Published to the public npm registry under the `@ceralive` scope as **built ESM + `.d.ts`** across seven entry points — see [`control/README.md`](control/README.md). | | [`cli/`](cli/) | **`modem-control`** (bench CLI) | The iteration surface: `probe`, `watch`, `apply`, `set-usb-mode`, `usage`, `certify`, `hil-cycle`. Compiled for `arm64` + `amd64` and run against real modems on a bench device to mature the package, capture per-SKU certification bundles, and prove hub VBUS port-cycling ([RB-10](docs/BENCH.md#rb-10--hub-vbus-verification-partial)). | -| [`packaging/`](packaging/) | **ModemManager stack `.deb`s** | Bookworm rebuilds of ModemManager + libmbim + libqmi + libqrtr-glib — **packaging only, not a fork**. libmbim, libqmi, and libqrtr-glib remain source-unmodified; ModemManager carries one owner-approved, three-patch BELABOX-derived FM350-GL series, hardware-validated on the carrier-mounted USB composition after restoring BELABOX's `ATZ0` first-enable override (see [`POLICY.md`](POLICY.md) and the [ADR](docs/adr/ADR-FM350-RNDIS-BEARER.md)). Provenance-verified upstream pins; installed on the bench from CI artifacts. | +| [`packaging/`](packaging/) | **ModemManager stack `.deb`s** | Trixie rebuilds of ModemManager + libmbim + libqmi + libqrtr-glib — **packaging only, not a fork**. libmbim, libqmi, and libqrtr-glib remain source-unmodified; ModemManager carries one owner-approved, three-patch BELABOX-derived FM350-GL series, hardware-validated on the carrier-mounted USB composition after restoring BELABOX's `ATZ0` first-enable override (see [`POLICY.md`](POLICY.md) and the [ADR](docs/adr/ADR-FM350-RNDIS-BEARER.md)). Provenance-verified upstream pins; installed on the bench from CI artifacts. | The control package's existing `./hardware` entry point also exposes transport-free SIM-presence and Huawei/ZTE/UFI response normalization. Device I/O, sessions, retries, @@ -87,7 +87,8 @@ modem-stack/ ``` `control/` and `cli/` form a single **Bun** workspace (Bun 1.4.0, strict TypeScript 7.0.2, -Biome via `@ceralive/biome-config`). `packaging/` is built in a bookworm container. +Biome via `@ceralive/biome-config`). `packaging/` is built in a trixie container — the suite +the device image runs. The two AST-backed source-shape guard tests use the test-only TypeScript 6 compiler-API compatibility package; workspace typechecking and package emit remain TypeScript 7. diff --git a/bun.lock b/bun.lock index 3b2a7d4..fbbc16f 100644 --- a/bun.lock +++ b/bun.lock @@ -14,7 +14,7 @@ }, "cli": { "name": "modem-control-cli", - "version": "1.3.0", + "version": "1.4.0", "bin": { "modem-control": "./src/index.ts", }, @@ -25,7 +25,7 @@ }, "control": { "name": "@ceralive/modem-control", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@httptoolkit/dbus-native": "0.1.5", "zod": "4.4.3", diff --git a/cli/package.json b/cli/package.json index 5df92b4..371351c 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "modem-control-cli", - "version": "1.3.0", + "version": "1.4.0", "private": true, "type": "module", "description": "modem-control bench CLI — probe/watch/apply/set-usb-mode/usage/certify/hil-cycle against real modems (the bench iteration surface).", diff --git a/cli/smoke/run.sh b/cli/smoke/run.sh index 3b74400..24a4f51 100755 --- a/cli/smoke/run.sh +++ b/cli/smoke/run.sh @@ -11,7 +11,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" DIST="${ROOT}/cli/dist" -ARM64_IMAGE="${SMOKE_ARM64_IMAGE:-debian:bookworm-slim}" +ARM64_IMAGE="${SMOKE_ARM64_IMAGE:-debian:trixie-slim}" "${ROOT}/cli/smoke/build-binaries.sh" diff --git a/control/package.json b/control/package.json index 2505c7f..d9d56bf 100644 --- a/control/package.json +++ b/control/package.json @@ -1,6 +1,6 @@ { "name": "@ceralive/modem-control", - "version": "1.3.0", + "version": "1.4.0", "type": "module", "description": "Cellular modem control for CeraLive — ModemManager D-Bus backend, NetworkManager adapter, desired-state reconciler, USB composition-mode model, data-usage sampler.", "license": "AGPL-3.0", diff --git a/docs/BENCH.md b/docs/BENCH.md index c960c90..6a604cb 100644 --- a/docs/BENCH.md +++ b/docs/BENCH.md @@ -187,12 +187,12 @@ b=$(sed 's/.*: *//' A6.3/slot-uid-before.txt); a=$(sed 's/.*: *//' A6.3/slot-uid 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.2. This -is the on-hardware counterpart of A5.2's daemon smoke (which runs in a `debian:bookworm` +is the on-hardware counterpart of A5.2's daemon smoke (which runs in a `debian:trixie` container in CI). **Preconditions** -- Clean Debian bookworm bench device (`arm64` for the shipping target; `amd64` acceptable +- Clean Debian trixie bench device (`arm64` for the shipping target; `amd64` acceptable for the desk proxy). `gh` authenticated, or the artifact zip already copied to the device. **Commands** @@ -218,7 +218,7 @@ gh release download v0.2.0 --repo CERALIVE/modem-stack --dir . --clobber ARCH=$(dpkg --print-architecture) # arm64 on the shipping SBC sudo apt-get update -# Post-bump every source outranks bookworm stock (see the note below), so installing the +# Post-bump every source outranks the suite 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 .. @@ -247,7 +247,7 @@ grep -q 'mmcli 1\.24\.2' A6.3/mm-version.txt \ && echo "RB-3 PASS" || echo "RB-3 FAIL" ``` -> **Direction (post-bump):** all four sources now sort **above** bookworm stock — +> **Direction (post-bump):** all four sources now sort **above** the suite 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 @@ -487,7 +487,7 @@ grep -q aarch64 A6.3/arm64-uname.txt \ ## RB-8 — Daemon smoke on real hardware `[PARTIAL]` The full A5.2 daemon smoke (system D-Bus + polkit + NetworkManager 1.42, `busctl` -introspect, udev/FCC/GIR/Vala paths) re-run on a real device rather than the CI bookworm +introspect, udev/FCC/GIR/Vala paths) re-run on a real device rather than the CI trixie container — the last check that the packaged stack is coherent end-to-end on hardware. **Preconditions** @@ -658,7 +658,7 @@ a fake runner, but nothing in CI can prove a physical hub drops VBUS. the bench image** — the USB tree comes from the `/sys/bus/usb/devices/*` sweep (RB-9). - `uhubctl` installed and runnable as root. **It is NOT on the bench image and is NOT in the image's apt archive** (`apt-cache show uhubctl` → `E: No packages found`, verified - 2026-08-16 on `ceralive2`), so it must be installed for the bench run — from a bookworm + 2026-08-16 on `ceralive2`), so it must be installed for the bench run — from a Debian archive that carries it, or built from source. `uhubctl` needs `sudo` (or a udev permissions rule); the PowerHook never escalates on its own. - The compiled `modem-control` binary (RB-1 preconditions). @@ -1698,5 +1698,5 @@ executed run the same directory also carries `{mmcli-list,certify,usb-devices}.t Every row stays `[PARTIAL]` until its evidence artifact is captured on a real bench device and its machine check prints `PASS`. No row may be claimed `[EXISTS]` on the strength of the CI proxy alone — the CI proxy is green (compiled probe smoke both arches, packaging contract -+ daemon smoke in a bookworm container, the full `bun test` suite), but the hardware evidence ++ daemon smoke in a trixie container, the full `bun test` suite), but the hardware evidence is what closes each gate. diff --git a/docs/ESIM-DECISION.md b/docs/ESIM-DECISION.md index 67bdd6f..4842fc5 100644 --- a/docs/ESIM-DECISION.md +++ b/docs/ESIM-DECISION.md @@ -481,15 +481,15 @@ package first, first-party build only if that fails: 1. **Stock Debian package — preferred, but currently unavailable to us.** Ruled out by *suite*, not by licence: 2.3.0-1 exists only in testing/unstable, and this stack's - packaging targets bookworm. Revisit on any suite move; this is the option that would + packaging targets trixie. Revisit on any suite move; this is the option that would cost the least. 2. **Upstream's own `lpac_2.3.0_arm64.deb` — rejected as a shipping form.** It is not built by us, not covered by `packaging/upstream-pins.yaml`'s four-link provenance chain, and not signed by our key. Adopting it would put a binary into the device image through a path every other `.deb` in this repo is forbidden to use. -3. **First-party bookworm rebuild in `modem-stack/packaging/` — the recommended form on a +3. **First-party target-suite rebuild in `modem-stack/packaging/` — the recommended form on a future GO.** It is not a new model: Debian now maintains a `debian/` recipe (2.3.0-1) - that can be pinned and rebuilt for bookworm exactly like ModemManager, libmbim, libqmi, + that can be pinned and rebuilt for the target suite exactly like ModemManager, libmbim, libqmi, and libqrtr-glib already are — same zero-patch rule, same provenance chain, same two-set package model. The one genuinely new requirement is the AGPL §6(d) source channel from §9.3. diff --git a/docs/FM350-DECISION.md b/docs/FM350-DECISION.md index 6965c36..4a524da 100644 --- a/docs/FM350-DECISION.md +++ b/docs/FM350-DECISION.md @@ -284,7 +284,7 @@ pass. ### Patched ModemManager HIL attempt, 2026-08-23 The owner-approved three-patch series was rebuilt locally for arm64 using the documented -`packaging/ci/build-bookworm.sh arm64` bench path. No release, package publication, or apt +`packaging/ci/build-stack.sh arm64` bench path. No release, package publication, or apt dispatch occurred. The board started on `modemmanager` and `libmm-glib0` `1.24.2-2~ceralive0.2.0`; the local dev packages were installed with `dpkg -i`, udev was reloaded, and ModemManager was restarted. diff --git a/docs/adr/ADR-FM350-RNDIS-BEARER.md b/docs/adr/ADR-FM350-RNDIS-BEARER.md index a9e08ff..6b32778 100644 --- a/docs/adr/ADR-FM350-RNDIS-BEARER.md +++ b/docs/adr/ADR-FM350-RNDIS-BEARER.md @@ -259,7 +259,7 @@ proceed without pretending the draft has been filed. lists one patch per logical change. Every DEP-3-style header names this ADR, exact BELABOX origin SHA(s), BELABOX authorship, rationale and the honest upstream-status verdict. This first non-empty source series also updates `README.md`, `packaging/README.md`, - `packaging/BOOKWORM-ADAPTATIONS.md`, `AGENTS.md` and `POLICY.md` in the same change under + `packaging/SUITE-ADAPTATIONS.md`, `AGENTS.md` and `POLICY.md` in the same change under Rule A. 5. **Prove it on the board before claiming it.** The acceptance test is not "it builds." It is: the FM350 binds the `fm350gl` plugin instead of `generic`, the bearer connects, and diff --git a/package.json b/package.json index 199f6bd..b76f17b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "modem-stack", - "version": "1.3.0", + "version": "1.4.0", "private": true, "type": "module", "description": "Cellular modem control for CeraLive — @ceralive/modem-control library, bench CLI, and ModemManager-stack .deb packaging (Phase A, standalone).", diff --git a/packaging/BOOKWORM-ADAPTATIONS.md b/packaging/BOOKWORM-ADAPTATIONS.md deleted file mode 100644 index e188636..0000000 --- a/packaging/BOOKWORM-ADAPTATIONS.md +++ /dev/null @@ -1,180 +0,0 @@ -# Bookworm adaptations - -The four sources are rebuilt from their **pinned sid/trixie `debian/-` packaging** -([`upstream-pins.yaml`](upstream-pins.yaml) `salsa_commit_sha`). libmbim, libqmi, and -libqrtr-glib carry zero source patches. ModemManager carries only the project-owner-approved, -three-patch BELABOX-derived FM350-GL series documented in -[`ADR-FM350-RNDIS-BEARER.md`](../docs/adr/ADR-FM350-RNDIS-BEARER.md) and -[`POLICY.md`](../POLICY.md). The sid -packaging does not build unmodified on bookworm, so a small set of **packaging-metadata** -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, and no symbols -regeneration. This file records every adaptation and the one approved source series. - -Each `debian/` dir here is byte-identical to the pinned salsa commit **except** for the hunks -and ModemManager patch series 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. - -**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). - -## The GObject-introspection adaptation — ALL FOUR sources (NEW this bump) - -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`: - -> `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".) - -That switch is a **Debian archive/cross-build policy** choice, **not** an upstream feature -requirement. On bookworm it is unsatisfiable two ways: - -- `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). - -### Adaptation — `debian/control` (each source) - -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 — `debian/rules` - -Two meson flags added to `override_dh_auto_configure` (companions to adaptation 2): - -``` -+ -Dsystemdsystemunitdir=/usr/lib/systemd/system \ -+ -Dudevdir=/usr/lib/udev \ -``` - -- **`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 *"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. - -## ModemManager — approved FM350-GL source patch series - -`ModemManager/debian/patches/series` carries exactly three patches against the pinned -ModemManager 1.24.2 source: - -1. A 1.24-native forward-port of BELABOX's FM350-GL USB/RNDIS bearer and `+GTACT` mode - handling, derived from commits `da01610c46c581b0c6f2acd0ac50f5bba666efdf`, - `419dc598d3f2c11f479dacdc2f6ef0e787e6ea3b`, - `90bcd376405906d3a94b0c239689eef1a3899ed2`, - `9e2bc4992a251b02f75280a2d2b1020227d2bfe8`, and - `9716d38b6a81a79b47a16ea96c27164219de6739`. -2. The FM350 CPOL crash-disable row from - `b4377c5028a0de4435b86f7aad9114e9443d69e4`. -3. The extended `+COPS` parser fix and regression test from - `43e09a768e3855f3afb93e517902c1e0e25ed676`. - -All three retain BELABOX author `rationalsa ` and record -`Forwarded: no`: the upstream offer is drafted but not filed. The project owner approved -this exact carry on 2026-08-22 under the narrow POLICY exception. Builds on amd64 and arm64 -prove source compatibility only; the real-board bearer drill remains required before any -hardware support claim. - -## Summary of the deviation surface - -| Source | `debian/control` | `debian/rules` | Source patches | -|---------------|----------------------------------------------------|---------------------------|----------------| -| ModemManager | GI-1.74 swap; debhelper `13.11.6`→`13.11.4`; `systemd-dev`→`udev` | +2 meson install-dir pins | 3-patch FM350-GL series above | -| libmbim | GI-1.74 swap | (pristine) | none | -| libqmi | GI-1.74 swap | (pristine) | none | -| libqrtr-glib | GI-1.74 swap | (pristine) | none | - -No other `debian/` file differs from the pinned salsa tree for any source (verified for the -pre-series adaptation baseline by: -`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/README.md b/packaging/README.md index f4b332e..97961e7 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -1,6 +1,6 @@ # packaging -Bookworm rebuilds of the ModemManager stack — **packaging only, not a fork**. libmbim, +Trixie rebuilds of the ModemManager stack — **packaging only, not a fork**. libmbim, libqmi, and libqrtr-glib remain source-unmodified; ModemManager carries one owner-approved, three-patch BELABOX-derived FM350-GL series (see `POLICY.md` and [`ADR-FM350-RNDIS-BEARER.md`](../docs/adr/ADR-FM350-RNDIS-BEARER.md)). Bench devices install @@ -29,7 +29,7 @@ Debian packaging tag. The authoritative pin manifest is [`upstream-pins.yaml`](u re-verified end-to-end by [`ci/verify-upstream-pins.sh`](ci/verify-upstream-pins.sh). > **Status:** `[EXISTS]` — provenance pin manifest + verifier, the four `debian/` recipes, -> the bootstrap-order container build (`ci/build-bookworm.sh`, amd64 + QEMU arm64), and the +> the bootstrap-order container build (`ci/build-stack.sh`, amd64 + QEMU arm64), and the > package **contract suite** (`ci/test-package-contract.sh`) + **daemon smoke** > (`ci/daemon-smoke.sh`) all landed. Both arches build clean with the exact 9-package runtime > closure; the contract suite is green on amd64 (full) and arm64 (metadata). The @@ -44,13 +44,13 @@ Each source's `debian/` dir is checked in at `/debian/` (`ModemManager`, libqrtr-glib carry no source patches. ModemManager adds only the three entries in `ModemManager/debian/patches/series`, each attributed to exact BELABOX commits and governed by the accepted FM350 ADR. -All four sources carry one shared bookworm adaptation — the GObject-introspection build-deps +All four sources carry one shared suite 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 +to the older `gobject-introspection` + `libgirepository1.0-dev` spelling, which remains a real +and current build-dep on trixie. 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). +documented, with rationale, per-suite re-measurement, and diff shape, in +[`SUITE-ADAPTATIONS.md`](SUITE-ADAPTATIONS.md). ### Approved ModemManager source series @@ -64,11 +64,11 @@ is build-tested and hardware-validated on the carrier-mediated USB topology; no claim exists until the production-topology board drill proves plugin binding, bearer activation, and routable IPv4. -[`ci/build-bookworm.sh`](ci/build-bookworm.sh) `` rebuilds the **selected** -sources in a `debian:bookworm` container in the mandatory bootstrap order +[`ci/build-stack.sh`](ci/build-stack.sh) `` rebuilds the **selected** +sources in a `debian:$TARGET_SUITE` container (default `trixie`) in the mandatory bootstrap order `libqrtr-glib → libmbim → libqmi → modemmanager`. Each source's freshly built `.deb`s feed a temporary **local apt repo** so the next source resolves its build-deps against them (not the -older bookworm-main versions). Arches are **native** — amd64 directly, arm64 via full-system +older suite-archive versions). Arches are **native** — amd64 directly, arm64 via full-system QEMU (`--platform linux/arm64`) — never cross-built. On a dev run the script injects `~ceralive0.0.0~dev` (via [`ci/inject-deb-version.sh`](ci/inject-deb-version.sh)), runs real `dpkg-buildpackage`, and asserts the **9-package runtime closure** (drift ⇒ non-zero exit). @@ -92,7 +92,7 @@ wired in `release.yml`'s `build-deb`: `=changed|unchanged` line per source plus `mode=differential|force-all`. A source is `changed` when the diff touched its `packaging//**` recipe or its own block-scoped entry in `upstream-pins.yaml`. It is **fail-SAFE toward rebuilding**: a shared - input change (`packaging/ci/**`, `BOOKWORM-ADAPTATIONS.md`), an absent previous release or + input change (`packaging/ci/**`, `SUITE-ADAPTATIONS.md`), an absent previous release or manifest, a v1-shaped manifest, or `FORCE_REBUILD=all` each force-all. A wrong `unchanged` would ship stale bytes; a wrong `changed` only costs build time. 3. **Stage the carry-forward** — [`ci/stage-carryforward-debs.sh`](ci/stage-carryforward-debs.sh) @@ -103,7 +103,7 @@ wired in `release.yml`'s `build-deb`: naming the row. This step runs **strictly before any build**: the carried debs are a build INPUT, because the builder seeds its Pin-Priority-1001 local apt repo from `build//`, so a changed source resolves its build-deps and gir typelibs against the carried - `-dev`/`gir1.2-*` packages rather than stock bookworm. Staging late still goes green and + `-dev`/`gir1.2-*` packages rather than the stock suite. Staging late still goes green and silently reintroduces stock dependencies, which is why the ordering is pinned by [`ci/test-release-workflow-wiring.sh`](ci/test-release-workflow-wiring.sh). 4. **Build the selected set** — `VERDICTS_FILE` supplies the build set explicitly. Skipped @@ -270,7 +270,7 @@ closed naming that source. The migration-continuity chain | [`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); `read-pin.sh --list-sources` prints the pinned source NAMES one per line, exposing the reader's own `yaml_sources` so an iterating caller (`check-upstream-freshness.sh`) needs no second copy of the YAML parser. 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/detect-changed-sources.sh`](ci/detect-changed-sources.sh) | **Step 1 of the differential pipeline.** Prints `=changed\|unchanged` for all four sources in bootstrap order plus `mode=differential\|force-all`; `--out ` also writes them for a later job step. A source is `changed` when the diff `..HEAD` touched `packaging//**` or its own **block-scoped** entry in `upstream-pins.yaml` (so a comment edit or a neighbour's pin bump does not implicate it). Force-all — every source `changed`, reason logged — on a shared-input change (`packaging/ci/**`, `BOOKWORM-ADAPTATIONS.md`), an absent previous release or manifest, a **v1-shaped** manifest (no `closure_version:` header — an absent header IS version 1), or `FORCE_REBUILD=all`. Fail-SAFE means REBUILD EVERYTHING: a wrong `unchanged` ships stale bytes, a wrong `changed` only costs build time. The previous release is resolved with `gh release list`, never `git describe` (latest PUBLISHED release ≠ nearest tag). Offline seams: `PREV_TAG`, `PREV_MANIFEST_FILE`, `HEAD_REF`, `GH_REPO`. The companion is never enumerated — it always rebuilds. | +| [`ci/detect-changed-sources.sh`](ci/detect-changed-sources.sh) | **Step 1 of the differential pipeline.** Prints `=changed\|unchanged` for all four sources in bootstrap order plus `mode=differential\|force-all`; `--out ` also writes them for a later job step. A source is `changed` when the diff `..HEAD` touched `packaging//**` or its own **block-scoped** entry in `upstream-pins.yaml` (so a comment edit or a neighbour's pin bump does not implicate it). Force-all — every source `changed`, reason logged — on a shared-input change (`packaging/ci/**`, `SUITE-ADAPTATIONS.md`), an absent previous release or manifest, a **v1-shaped** manifest (no `closure_version:` header — an absent header IS version 1), or `FORCE_REBUILD=all`. Fail-SAFE means REBUILD EVERYTHING: a wrong `unchanged` ships stale bytes, a wrong `changed` only costs build time. The previous release is resolved with `gh release list`, never `git describe` (latest PUBLISHED release ≠ nearest tag). Offline seams: `PREV_TAG`, `PREV_MANIFEST_FILE`, `HEAD_REF`, `GH_REPO`. The companion is never enumerated — it always rebuilds. | | [`ci/test-detect-changed-sources.sh`](ci/test-detect-changed-sources.sh) | The detector's contract — builds its own throwaway git repo and stubs `gh` on `PATH`, so no docker, no network and no built `.deb`. Runs in the lightweight PR lane. | | [`ci/stage-carryforward-debs.sh`](ci/stage-carryforward-debs.sh) | **Step 2 of the differential pipeline.** Stages EVERY row of each `unchanged` source — runtime **and** aux (`-dbgsym`, `-dev`, `gir1.2-*`); the `role` column is deliberately not filtered on, or a release would silently drop ~36 debs — from the previous release into `build//`, sha256-verified against its manifest row. Reads the verdict stream on stdin or `--verdicts ` rather than re-running detection, which must happen exactly once per run. It reverses the uploader's `~`→`.` asset-name sanitization by turning each `~` into a single-char `?` glob (a blanket `.`→`~` would hit legitimate dots) and requires EXACTLY ONE match; the staged file always lands under the canonical `~` name. Fails closed naming the offending row on a missing/ambiguous asset, a sha256 mismatch, an unknown source, an `unchanged` source with zero previous rows, a v1-shaped manifest, or a destination file with different bytes. The companion row is skipped explicitly. `mode=force-all` stages nothing and exits 0 without reading a manifest at all. | | [`ci/test-stage-carryforward-debs.sh`](ci/test-stage-carryforward-debs.sh) | The stager's contract — builds its own fixture manifest + GitHub-mangled asset dir and drives the script through the `PREV_MANIFEST_FILE` / `CARRYFORWARD_ASSET_DIR` seams. No docker, no network, no built `.deb`. | @@ -279,20 +279,20 @@ closed naming that source. The migration-continuity chain | [`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/check-upstream-freshness.sh`](ci/check-upstream-freshness.sh) | The **upstream freshness watch** behind [`.github/workflows/upstream-watch.yml`](../.github/workflows/upstream-watch.yml). For every source it enumerates the upstream release tags and the salsa `debian/*` packaging tags with `git ls-remote --tags`, keeps only STABLE members, and compares them to the pins (read through `read-pin.sh` — it parses no YAML itself). Three verdicts: `current`, `behind ()`, and the deliberately distinct `upstream-ahead-no-packaging ()` — upstream released but Debian has not packaged it, so there is no `-` pair to pin and no bump to recommend. The stable filter rejects `-rc`/`-dev`/`-alpha`/`-beta`/`-pre`, anything that is not a plain `X.Y.Z`, **odd-minor development series** (the GNOME/freedesktop convention all four projects follow — ModemManager `1.25.95` is the unstable train toward 1.26.0 and went to Debian *experimental*), `.9x` snapshot micros, and `~`-bearing Debian revisions. Exit `0` = no bump, `10` = at least one source behind. `--dry-run` prints the would-be issue body and makes no API call; **the script is issue-only and can neither edit a pin nor dispatch a build** (locked by a source-scan fence in its test). | | [`ci/test-check-upstream-freshness.sh`](ci/test-check-upstream-freshness.sh) | The watch's **offline** contract — no network, no container. Drives the script through its documented `UPSTREAM_FRESHNESS_FIXTURE_DIR` seam, which swaps `git ls-remote` for verbatim fixture output so the tag parse under test is the real one. Cases: all-current ⇒ no issue body; upstream `1.26.0` + `debian/1.26.0-1` ⇒ `behind` with both versions named in the body; **only `1.25.95` ⇒ still `current`** (the real dev-series trap, proven on both the upstream tag and the `debian/1.25.95-1` experimental tag); newer upstream with no packaging tag ⇒ `upstream-ahead-no-packaging`, never `behind`; `-rc`/`-dev`/`.90`/`v`-prefixed noise ⇒ each rejected on its own named reason; packaging-only revision bump ⇒ `behind` naming the packaging tag; plus the issue-only source fence with a two-way non-vacuity control. | -| [`ci/build-bookworm.sh`](ci/build-bookworm.sh) | **Step 3 of the differential pipeline.** Rebuilds the SELECTED 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. The build set comes from `VERDICTS_FILE` (the detector's output) or an explicit `BUILD_SOURCES` list; supplying both fails closed, supplying neither builds all four (the local-dev default, never what CI relies on). Before the first build it seeds the Pin-Priority-1001 local repo with every already-staged deb of each SKIPPED source, so a selected source resolves stack build-deps and gir typelibs against carried CeraLive packages rather than stock bookworm; carried debs are preserved and only stale `*.changes`/`*.buildinfo` plus the selected sources' own artifacts are removed. Release builds call `inject-deb-version.sh` once per selected source, passing the caller's already-resolved `PREV_MANIFEST_FILE`. The runtime closure is asserted from the **MERGED** staged set (built + carried), and a zero-source run starts no container yet still performs that assertion. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → that source's derived `~ceralive.N`; 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 per-source package-set **equality** (via `ci/check-package-sets.sh`) from each freshly built source's `.changes` plus the 9-package runtime closure over the merged staged set (drift ⇒ non-zero). Output to gitignored `build//`. | -| [`ci/test-build-bookworm-differential.sh`](ci/test-build-bookworm-differential.sh) | The differential builder's contract. Its `BUILD_BOOKWORM_STUB_DIR` seam replaces only the expensive source-build body with source-keyed fixture artifacts; build-set parsing, carry seeding, bootstrap dispatch, counter derivation, package-set checking and the merged-closure assertion all run through their production paths. No docker, no network. | -| [`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. It also runs the registered offline suites for tag guarding, change detection, carry-forward staging, differential builds, suffix coherence, release wiring, upstream freshness, and the FM350 patch contract. The deb-consuming contract lives in `test-package-contract.sh` + `daemon-smoke.sh`. | -| [`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`); **per-source** coherence (one `~ceralive` suffix WITHIN each upstream source — two sources at different rebuild counters is what a differential release produces and is accepted; the retained negative is now a source disagreeing with ITSELF, and it fails closed naming that source); real ordering proofs including the migration-continuity chain; 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/build-companion.sh`](ci/build-companion.sh) | Builds the first-party `ceralive-modem-support` companion `.deb` — ONCE, `Architecture: all`, into `build/all/`. Container by default (`debian:bookworm`), `--native` for the local QA loop. `RELEASE_VERSION=vX.Y.Z` → Version `X.Y.Z` (unset → `0.0.0~dev`), injected into a COPY of the changelog. Fails closed if the produced deb's `Architecture` is not `all` or its `Version` is not the requested one. | +| [`ci/build-stack.sh`](ci/build-stack.sh) | **Step 3 of the differential pipeline.** Rebuilds the SELECTED sources in a `debian:$TARGET_SUITE` container (default `trixie`) in bootstrap order via a temporary local apt repo. `build-stack.sh ` — native amd64 or full-system-QEMU arm64, never cross-built. The build set comes from `VERDICTS_FILE` (the detector's output) or an explicit `BUILD_SOURCES` list; supplying both fails closed, supplying neither builds all four (the local-dev default, never what CI relies on). Before the first build it seeds the Pin-Priority-1001 local repo with every already-staged deb of each SKIPPED source, so a selected source resolves stack build-deps and gir typelibs against carried CeraLive packages rather than the stock suite; carried debs are preserved and only stale `*.changes`/`*.buildinfo` plus the selected sources' own artifacts are removed. Release builds call `inject-deb-version.sh` once per selected source, passing the caller's already-resolved `PREV_MANIFEST_FILE`. The runtime closure is asserted from the **MERGED** staged set (built + carried), and a zero-source run starts no container yet still performs that assertion. Fetches + sha256-verifies each pinned `.orig.tar`, overlays the checked-in `debian/`, injects the version (`RELEASE_VERSION=vX.Y.Z` → that source's derived `~ceralive.N`; 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 `dh_girepository` can resolve cross-namespace typelib deps — Qmi→Qrtr, MM→Qmi/Mbim/Qrtr), runs real `dpkg-buildpackage`, and asserts per-source package-set **equality** (via `ci/check-package-sets.sh`) from each freshly built source's `.changes` plus the 9-package runtime closure over the merged staged set (drift ⇒ non-zero). Output to gitignored `build//`. | +| [`ci/test-build-stack-differential.sh`](ci/test-build-stack-differential.sh) | The differential builder's contract. Its `BUILD_STACK_STUB_DIR` seam replaces only the expensive source-build body with source-keyed fixture artifacts; build-set parsing, carry seeding, bootstrap dispatch, counter derivation, package-set checking and the merged-closure assertion all run through their production paths. No docker, no network. | +| [`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-stack.sh` in-container after the closure check, and standalone per-arch. | +| [`ci/contract.sh`](ci/contract.sh) | The packaging **PR lane** (target-suite 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. It also runs the registered offline suites for tag guarding, change detection, carry-forward staging, differential builds, suffix coherence, release wiring, upstream freshness, and the FM350 patch contract. The deb-consuming contract lives in `test-package-contract.sh` + `daemon-smoke.sh`. | +| [`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:$TARGET_SUITE` container and runs: metadata/arch over the 9-package closure (revision-exact — every deb's base must equal its `read-pin.sh` `-`); clean-suite `apt-get install ./*.deb`; upgrade (the suite's stock modemmanager → the 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`); **per-source** coherence (one `~ceralive` suffix WITHIN each upstream source — two sources at different rebuild counters is what a differential release produces and is accepted; the retained negative is now a source disagreeing with ITSELF, and it fails closed naming that source); real ordering proofs including the migration-continuity chain; 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 (trixie 1.52.1) 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 adaptation fails closed here). amd64 by default. | +| [`ci/build-companion.sh`](ci/build-companion.sh) | Builds the first-party `ceralive-modem-support` companion `.deb` — ONCE, `Architecture: all`, into `build/all/`. Container by default (`debian:$TARGET_SUITE`, default `trixie`), `--native` for the local QA loop. `RELEASE_VERSION=vX.Y.Z` → Version `X.Y.Z` (unset → `0.0.0~dev`), injected into a COPY of the changelog. Fails closed if the produced deb's `Architecture` is not `all` or its `Version` is not the requested one. | | [`ci/test-fcc-reconcile.sh`](ci/test-fcc-reconcile.sh) | The FCC reconciler's **behaviour** contract, runnable on any host with no container and no root: every path is redirected into a scratch tree via `CERALIVE_FCC_{POLICY_FILE,AVAILABLE_DIR,ACTIVE_DIR}`. Covers absent/malformed/opt-out/idempotence, the one-model policy that must not parse as empty, the refused vendor-only key, an enabled model MM ships no script for, and both foreign-entry cases (a real file and a symlink pointing outside the available tier). Complements — never replaces — the chroot contract, which proves the same logic from the PACKAGED location after a real `dpkg` install. | | [`ci/test-companion-chroot.sh`](ci/test-companion-chroot.sh) | The companion's **CHROOT-stage** contract in a clean `debian:trixie` container: install / declared-inventory equality / chroot guard (+ non-vacuity) / single-owner `dpkg -S` / `/etc` override precedence / both override-removal branches / FCC absent-enabled-malformed matrix / upgrade with no conffile prompt / downgrade / purge with zero leftovers. Builds 0.9.0 and 1.1.0 so the upgrade and downgrade legs exercise real dpkg. The consumer stage is bench-gated. | | [`ci/companion-inventory.txt`](ci/companion-inventory.txt) | The companion's frozen declared file inventory, compared for EQUALITY by the chroot QA — an added, dropped or relocated asset fails the gate naming itself. | | [`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 `build_arch package source version role filename sha256` matrix Phase-B apt publication AND `create-release` asset reconciliation consume. Emits **`closure_version: 2`** — the versioned contract apt-worker validates against — which adds the `Architecture: all` companion as ONE row with `build_arch` `all`, alongside the unchanged 9 × 2 arch-dependent rows. Build architecture and index membership are separate: `all` enters EVERY index arch, anything else its own. Per-source equality is scoped by `[arch-all sources]` so `build/all` is checked against the companion only and `build/` against the four upstream sources only. Fails closed on any set drift. dpkg-free (filename parse + `sha256sum`), so it runs anywhere. Emits **`suffix_scheme: per-source-counter`** and NO `deb_version_suffix:` — under per-source rebuild counters no single suffix value is truthful, so the header states the scheme and each row keeps carrying its own version (apt-worker's validator reads neither field). | | [`ci/suffix-contract.sh`](ci/suffix-contract.sh) | SOURCED library — the `~ceralive` suffix contract in ONE place. `assert_group_coherence =…` groups packages by owning source (derived from `expected-packages.txt`, never a second frozen list) and asserts each source's OWN internal coherence, so differing counters ACROSS sources pass while a source disagreeing with itself fails closed naming it. `prove_chain_ordered ` runs the **migration-continuity chain** — `~ceralive0.2.0 < ~ceralive1.0.0 < ~ceralive1.1.0 < ~ceralive.1 < ~ceralive.2 < ~ceralive.10 < -` — through real `dpkg --compare-versions`; its legacy members are every version that exists as a published artifact today, so the chain is the proof that every fleet device upgrades cleanly into the counter scheme. Sourced by `test-package-contract.sh` (CHECK 5/6) and `test-suffix-coherence-manifest.sh` so the container lane and the host lane cannot prove different rules. | | [`ci/test-suffix-coherence-manifest.sh`](ci/test-suffix-coherence-manifest.sh) | The per-source-suffix + mixed-version-manifest contract — host-runnable, offline, **no docker**. Proves: sources at differing counters are accepted while an internally-mixed source fails closed naming itself; the full migration chain under real `dpkg` (with a non-vacuity control showing a lexical compare inverts `.2` vs `.10`); `generate-release-manifest.sh` over a MIXED staged set emits the new header, no `deb_version_suffix:`, and a row per staged deb at each source's own version; and a ZERO-UPSTREAM-BUILD set (only the companion fresh) keeps every upstream row at its carried counter with only the companion at the bare tag version. Every expected count is COUNTED from the fixture that produced it — no total is written down. | -| [`ci/test-release-workflow-wiring.sh`](ci/test-release-workflow-wiring.sh) | The **static** proof that `release.yml`'s `build-deb` is wired the way the differential pipeline needs. It reads the workflow text and never dispatches a run: step presence, the ordering chain (previous-release resolution → detection → carry-forward staging → **every** executable `build-bookworm.sh` mention, comment lines excluded), `fetch-depth: 0` inside the `build-deb` slice specifically, and an awk detector for any `${{ }}` interpolated into a `run:` body. It exists because the invariant it guards fails SILENTLY — staging after the build still produces a green release, built against stock bookworm dependencies. `RELEASE_WORKFLOW_FILE` points it at a scratch copy for failure demonstrations, so the tracked workflow is never mutated. | +| [`ci/test-release-workflow-wiring.sh`](ci/test-release-workflow-wiring.sh) | The **static** proof that `release.yml`'s `build-deb` is wired the way the differential pipeline needs. It reads the workflow text and never dispatches a run: step presence, the ordering chain (previous-release resolution → detection → carry-forward staging → **every** executable `build-stack.sh` mention, comment lines excluded), `fetch-depth: 0` inside the `build-deb` slice specifically, and an awk detector for any `${{ }}` interpolated into a `run:` body. It exists because the invariant it guards fails SILENTLY — staging after the build still produces a green release, built against stock suite dependencies. `RELEASE_WORKFLOW_FILE` points it at a scratch copy for failure demonstrations, so the tracked workflow is never mutated. | | [`ci/test-fm350-patch-contract.sh`](ci/test-fm350-patch-contract.sh) | Static regression gate for the hardware-required FM350 first-enable override: the carried patch must install `enabling_modem_init`, send `Z0`, and never regress that override to core `Z` (which firmware `81600.0000.00.19.17.10` rejects with CME 59). | | [`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/SUITE-ADAPTATIONS.md b/packaging/SUITE-ADAPTATIONS.md new file mode 100644 index 0000000..7a0ccd6 --- /dev/null +++ b/packaging/SUITE-ADAPTATIONS.md @@ -0,0 +1,186 @@ +# Suite adaptations + +**Target suite: `trixie` (Debian 13).** It is the suite the CeraLive device image runs, and +the build container is `debian:$TARGET_SUITE` in +[`ci/build-stack.sh`](ci/build-stack.sh) — a variable, not a literal, because the build suite +and the target suite must agree. A stack built against a newer libc than the board ships +fails at load with a `GLIBC_` symbol error that no build step can observe, so +`build-stack.sh` asserts the container's own `VERSION_CODENAME` equals `TARGET_SUITE` and +**refuses the build** on drift. Parity is a gate here, not a convention. + +The four sources are rebuilt from their **pinned sid `debian/-` packaging** +([`upstream-pins.yaml`](upstream-pins.yaml) `salsa_commit_sha`). libmbim, libqmi, and +libqrtr-glib carry zero source patches. ModemManager carries only the project-owner-approved, +three-patch BELABOX-derived FM350-GL series documented in +[`ADR-FM350-RNDIS-BEARER.md`](../docs/adr/ADR-FM350-RNDIS-BEARER.md) and +[`POLICY.md`](../POLICY.md). + +That pinned packaging is **sid packaging, and sid is ahead of trixie** — so it does not build +unmodified here either. A small set of **packaging-metadata** adaptations is applied, limited +to the pre-authorized salsa-deviation class: `debian/control` build-dependency +relaxations/substitutions and `debian/rules` meson/install-path pins. Nothing else — no +maintainer-script changes, no package add/remove/rename, no `Breaks`/`Replaces`/`Conflicts` +edits, and no symbols regeneration. + +Each `debian/` dir here is byte-identical to the pinned salsa commit **except** for the hunks +and ModemManager patch series below. Symbols files are **byte-preserved** from the salsa tree; +any need to regenerate a symbols file is a HARD STOP, not a local fix. + +**Bases:** ModemManager `1.24.2-2`, libmbim `1.34.0-1`, libqmi `1.38.0-1`, +libqrtr-glib `1.4.0-1`. + +## Why the adaptation set survived the bookworm → trixie move unchanged + +These adaptations were originally derived to build sid packaging on **bookworm**. The obvious +expectation on moving to trixie is that they can all be reverted, since trixie is much closer +to sid. **That expectation is wrong for the GI adaptation and wrong for the install-dir pins**, +and each verdict below was re-measured in a `debian:trixie` container rather than reasoned +about. The measured environment: + +| Package | trixie | Consequence | +|---|---|---| +| `gobject-introspection` | `1.84.0-1` | sid's `(>= 1.80)` **is** satisfiable here | +| `libgirepository1.0-dev` | `1.84.0-1` | the adapted build-dep **still exists** on trixie | +| `gir1.2-glib-2.0-dev` | `2.84.4-3~deb13u3` | real package | +| `gir1.2-gio-2.0-dev` | **virtual** | `Provides:` of `gir1.2-glib-2.0-dev` | +| `gir1.2-gobject-2.0-dev` | **virtual** | `Provides:` of `gir1.2-glib-2.0-dev` | +| `debhelper` | `13.24.2` | satisfies both `>= 13.11.4` and `>= 13.11.6` | +| `systemd-dev` | `257.13-1~deb13u1` | present — unlike bookworm | +| `udev` | `257.13-1~deb13u1` | present, but ships **no `.pc` file at all** | +| `policykit-1` | **absent** | replaced by `polkitd` (see the smoke note below) | + +The decisive measurement is the last `udev` row. On bookworm the install-dir pins were needed +because `systemd.pc` was missing from `udev`; on trixie **both** `udev.pc` and `systemd.pc` +have moved into `systemd-dev`, so a `udev` build-dep supplies neither. The pins are therefore +*more* load-bearing on trixie than they were on bookworm, and adaptations 2 and 3 below are a +**coherent pair** — reverting the build-dep without the pins, or the pins without the +build-dep, breaks the build. + +**Verdict: the adaptation set is retained verbatim.** Every adapted `debian/control` resolves +on trixie (`apt-get build-dep --dry-run`, exit 0, all four sources), and the full four-source +build is green. Reverting the GI block would be a no-op at best (both forms resolve, because +the two sid-only `gir1.2-*` names are satisfied virtually) while re-opening a verified +deviation surface for nothing. + +## The GObject-introspection adaptation — ALL FOUR sources + +The DebianOnMobile maintainers modernized the introspection build-deps: 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`: + +> `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".) + +That switch is a **Debian archive/cross-build policy** choice, **not** an upstream feature +requirement — which is why substituting the older spelling is safe. + +### Adaptation — `debian/control` (each source) + +``` +- 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. + +### Why it is retained on trixie + +- **It resolves.** `libgirepository1.0-dev` is a real, current package on trixie (`1.84.0-1`), + so the adapted form needs no change. +- **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) — far below anything either spelling provides. +- **Functionally proven, not assumed.** The daemon smoke loads the built + `gir1.2-modemmanager-1.0` typelib through PyGObject and reads a real enum, and compiles + + links a Vala program against `libmm-glib` via `valac -C` → `cc $(pkg-config … mm-glib)`. + Both pass on trixie, so the introspection and VAPI outputs are ABI-valid, not merely present. + +## libqrtr-glib, libmbim, libqmi — only the GI adaptation + +Beyond the shared GI substitution, these three build unmodified. Their `control` carries no +trixie-unsatisfiable build-dep, 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 + +### 1. debhelper version relax — `debian/control` + +`debhelper (>= 13.11.6)` → `debhelper (>= 13.11.4)`. Compat level is unchanged +(`debhelper-compat (= 13)`). + +**On trixie this is a no-op**: debhelper is `13.24.2`, which satisfies the original floor. It +is retained only because reverting it changes a verified `debian/` tree to no effect. It is the +one adaptation here that could be dropped safely, and the one with the least reason to be. + +### 2. `systemd-dev` → `udev` build-dep — `debian/control` + +`libsystemd-dev (>= 209)` is kept unchanged (it links libsystemd for journal / suspend-resume +and is unrelated to `systemd-dev`). + +**On trixie `systemd-dev` exists**, so this substitution is no longer forced by availability. +It is retained as the working half of the coherent pair with adaptation 3: with `udev` alone, +neither `udev.pc` nor `systemd.pc` is present, and the pins below are what make that fine. + +### 3. systemd + udev install-dir pins — `debian/rules` + +Two meson flags in `override_dh_auto_configure` (companions to adaptation 2): + +``` ++ -Dsystemdsystemunitdir=/usr/lib/systemd/system \ ++ -Dudevdir=/usr/lib/udev \ +``` + +- **`systemdsystemunitdir`** — MM's `meson.build` (l.209) calls `dependency('systemd')` when + the option is left empty, to read `systemdsystemunitdir` from `systemd.pc`. Under adaptation + 2 that `.pc` is absent, so `dependency('systemd')` would fail *"systemd required but not + found"*. Pinning the dir makes meson skip the lookup entirely. +- **`udevdir`** — likewise skips the `udev.pc` lookup. Both pinned values are the usr-merged + paths the sid `modemmanager.install` hardcodes (`usr/lib/systemd`, `usr/lib/udev`), which are + correct on trixie. + +## ModemManager — approved FM350-GL source patch series + +`ModemManager/debian/patches/series` carries exactly three patches against the pinned +ModemManager 1.24.2 source: + +1. A 1.24-native forward-port of BELABOX's FM350-GL USB/RNDIS bearer and `+GTACT` mode + handling, derived from commits `da01610c46c581b0c6f2acd0ac50f5bba666efdf`, + `419dc598d3f2c11f479dacdc2f6ef0e787e6ea3b`, + `90bcd376405906d3a94b0c239689eef1a3899ed2`, + `9e2bc4992a251b02f75280a2d2b1020227d2bfe8`, and + `9716d38b6a81a79b47a16ea96c27164219de6739`. +2. The FM350 CPOL crash-disable row from + `b4377c5028a0de4435b86f7aad9114e9443d69e4`. +3. The extended `+COPS` parser fix and regression test from + `43e09a768e3855f3afb93e517902c1e0e25ed676`. + +All three retain BELABOX author `rationalsa ` and record +`Forwarded: no`. The project owner approved this exact carry on 2026-08-22 under the narrow +POLICY exception. Builds prove source compatibility only; the real-board bearer drill remains +required before any hardware support claim. + +## One CI-side trixie change that is NOT a packaging adaptation + +`ci/daemon-smoke.sh` installed `policykit-1`, which **does not exist on trixie** — it was a +bookworm transitional package and the daemon is now `polkitd`. The smoke installs `polkitd`. +This is test-harness plumbing, not a `debian/` deviation, and no source recipe references it. + +## Summary of the deviation surface + +| Source | `debian/control` | `debian/rules` | Source patches | +|---------------|----------------------------------------------------|---------------------------|----------------| +| ModemManager | GI swap; debhelper `13.11.6`→`13.11.4`; `systemd-dev`→`udev` | +2 meson install-dir pins | 3-patch FM350-GL series above | +| libmbim | GI swap | (pristine) | none | +| libqmi | GI swap | (pristine) | none | +| libqrtr-glib | GI swap | (pristine) | none | + +No other `debian/` file differs from the pinned salsa tree for any source. diff --git a/packaging/ci/build-companion.sh b/packaging/ci/build-companion.sh index 34f890e..9c289f9 100755 --- a/packaging/ci/build-companion.sh +++ b/packaging/ci/build-companion.sh @@ -11,7 +11,7 @@ # have to absorb a CeraLive-specific asset. # # USAGE build-companion.sh [--native] -# default: builds inside a `debian:bookworm` container (matches the upstream lane). +# default: builds inside a `debian:$TARGET_SUITE` container (matches the upstream lane). # --native: builds on the host (needs debhelper + dpkg-dev); used by the local QA harness. # # ENV @@ -25,7 +25,8 @@ PKG_ROOT="$(cd "$HERE/.." && pwd)" SRC_DIR="$PKG_ROOT/ceralive-modem-support" BUILD_ROOT="${BUILD_ROOT:-$PKG_ROOT/build}" OUT_DIR="$BUILD_ROOT/all" -IMAGE="${COMPANION_BUILD_IMAGE:-debian:bookworm}" +TARGET_SUITE="${TARGET_SUITE:-trixie}" +IMAGE="${COMPANION_BUILD_IMAGE:-debian:$TARGET_SUITE}" NATIVE=0 [[ "${1:-}" == "--native" ]] && NATIVE=1 diff --git a/packaging/ci/build-bookworm.sh b/packaging/ci/build-stack.sh similarity index 90% rename from packaging/ci/build-bookworm.sh rename to packaging/ci/build-stack.sh index fb8c50c..8756a7e 100755 --- a/packaging/ci/build-bookworm.sh +++ b/packaging/ci/build-stack.sh @@ -1,5 +1,12 @@ #!/usr/bin/env bash -# build-bookworm.sh — differential ModemManager-stack rebuild for bookworm. +# build-stack.sh — differential ModemManager-stack rebuild. +# +# TARGET SUITE +# The build container is `debian:$TARGET_SUITE`, default `trixie` — the suite the device +# image actually runs. The suite is a variable rather than a literal because the build suite +# and the target suite must agree: a binary built against a newer libc than the board ships +# fails at load with a GLIBC_ symbol error the build itself never sees. Overriding it is for +# bisecting a suite-specific build break, not for producing a release. # # BUILD SET # BUILD_SOURCES may be an explicitly set space/comma-separated list of upstream-pins.yaml @@ -12,7 +19,7 @@ # Selected sources build in the mandatory bootstrap order. Before the first build, every deb # belonging to each skipped source is copied from the already-staged output directory into the # temporary Pin-Priority 1001 local apt repo. A selected source therefore resolves stack -# build-deps and gir typelibs against carried CeraLive packages, never stock bookworm merely +# build-deps and gir typelibs against carried CeraLive packages, never the stock suite merely # because its dependency source was skipped. # # OUTPUT + ASSERTIONS @@ -28,7 +35,7 @@ # script never fetches the previous manifest. Dev builds retain ~ceralive0.0.0~dev. # # TEST SEAM -# BUILD_BOOKWORM_STUB_DIR is used only by test-build-bookworm-differential.sh. It replaces the +# BUILD_STACK_STUB_DIR is used only by test-build-stack-differential.sh. It replaces the # expensive build body with source-keyed fixture artifacts while exercising production build-set # parsing, seeding, dispatch, check-package-sets.sh, and merged-closure logic. # @@ -41,7 +48,10 @@ BUILD_ORDER=(libqrtr-glib libmbim libqmi ModemManager) EXPECTED_RUNTIME=(libmbim-glib4 libmbim-proxy libmbim-utils libmm-glib0 libqmi-glib5 \ libqmi-proxy libqmi-utils libqrtr-glib0 modemmanager) -LOG_PREFIX="build-bookworm" +TARGET_SUITE="${TARGET_SUITE:-trixie}" +BUILD_IMAGE="${BUILD_IMAGE:-debian:$TARGET_SUITE}" + +LOG_PREFIX="build-stack" build_log() { printf '%s: %s\n' "$LOG_PREFIX" "$*" >&2; } die() { printf '%s: %s\n' "$LOG_PREFIX" "$*" >&2 @@ -224,7 +234,7 @@ if [ "${BUILD_IN_CONTAINER:-0}" != 1 ]; then case "$ARCH" in amd64) PLATFORM="linux/amd64" ;; arm64) PLATFORM="linux/arm64" ;; - *) echo "usage: build-bookworm.sh " >&2; exit 2 ;; + *) echo "usage: build-stack.sh " >&2; exit 2 ;; esac PKG_ROOT="$SCRIPT_PKG_ROOT" @@ -232,7 +242,7 @@ if [ "${BUILD_IN_CONTAINER:-0}" != 1 ]; then mkdir -p "$OUT" clean_run_outputs "$OUT" "$PKG_ROOT/ci/expected-packages.txt" - build_log "arch=$ARCH platform=$PLATFORM" + build_log "arch=$ARCH platform=$PLATFORM suite=$TARGET_SUITE image=$BUILD_IMAGE" build_log "packaging root=$PKG_ROOT" build_log "output=$OUT" build_log "selected sources (${#BUILD_SOURCE_KEYS[@]}): ${CANONICAL_BUILD_SOURCES:-none}" @@ -259,6 +269,7 @@ if [ "${BUILD_IN_CONTAINER:-0}" != 1 ]; then -e ARCH="$ARCH" -e BUILD_SOURCES="$CANONICAL_BUILD_SOURCES" -e RELEASE_VERSION="${RELEASE_VERSION:-}" + -e TARGET_SUITE="$TARGET_SUITE" ) if [ -n "${PREV_MANIFEST_FILE:-}" ]; then docker_args+=( @@ -269,8 +280,8 @@ if [ "${BUILD_IN_CONTAINER:-0}" != 1 ]; then docker_args+=( -v "$PKG_ROOT":/pkg:ro -v "$OUT":/out - debian:bookworm - bash /pkg/ci/build-bookworm.sh "$ARCH" + "$BUILD_IMAGE" + bash /pkg/ci/build-stack.sh "$ARCH" ) docker "${docker_args[@]}" @@ -281,18 +292,18 @@ fi # ========================================================================================== # CONTAINER ROLE — seed skipped sources, then build only selected sources in bootstrap order. # ========================================================================================== -PKG_MOUNT="${BUILD_BOOKWORM_PKG_ROOT:-/pkg}" -OUT_DIR="${BUILD_BOOKWORM_OUT_DIR:-/out}" -STUB_BUILD_DIR="${BUILD_BOOKWORM_STUB_DIR:-}" +PKG_MOUNT="${BUILD_STACK_PKG_ROOT:-/pkg}" +OUT_DIR="${BUILD_STACK_OUT_DIR:-/out}" +STUB_BUILD_DIR="${BUILD_STACK_STUB_DIR:-}" [ -d "$PKG_MOUNT" ] || die "container packaging root '$PKG_MOUNT' is not a directory" [ -d "$OUT_DIR" ] || die "container output '$OUT_DIR' is not a directory" if [ -n "$STUB_BUILD_DIR" ] && [ ! -d "$STUB_BUILD_DIR" ]; then - die "BUILD_BOOKWORM_STUB_DIR='$STUB_BUILD_DIR' is set but not a directory" + die "BUILD_STACK_STUB_DIR='$STUB_BUILD_DIR' is set but not a directory" fi ARCH="${ARCH:-}" if [ -z "$ARCH" ]; then - [ -z "$STUB_BUILD_DIR" ] || die "ARCH is required with BUILD_BOOKWORM_STUB_DIR" + [ -z "$STUB_BUILD_DIR" ] || die "ARCH is required with BUILD_STACK_STUB_DIR" ARCH="$(dpkg --print-architecture)" fi @@ -307,9 +318,16 @@ fi if [ -n "$STUB_BUILD_DIR" ]; then echo "== stubbed in-container build (target=$ARCH) ==" - log "BUILD_BOOKWORM_STUB_DIR active: real apt and dpkg-buildpackage commands are disabled" + log "BUILD_STACK_STUB_DIR active: real apt and dpkg-buildpackage commands are disabled" else echo "== in-container build (arch=$(dpkg --print-architecture), target=$ARCH, $(uname -m)) ==" + # Build-suite parity is a GATE, not a convention: a stack built against a different suite + # than the device runs loads with a GLIBC_ symbol error no build step can observe. + container_suite="$(. /etc/os-release 2>/dev/null && printf '%s' "${VERSION_CODENAME:-}")" + [ -n "$container_suite" ] || refuse "container does not report VERSION_CODENAME; the build suite cannot be verified against TARGET_SUITE='$TARGET_SUITE'" + [ "$container_suite" = "$TARGET_SUITE" ] || + refuse "build-suite drift: container is '$container_suite' but TARGET_SUITE is '$TARGET_SUITE'" + log "build-suite parity OK: container suite '$container_suite' matches TARGET_SUITE" fi export DEBIAN_FRONTEND=noninteractive @@ -377,7 +395,7 @@ seed_carried_source() { # matches=("$OUT_DIR/${package}_"*.deb) shopt -u nullglob if [ "${#matches[@]}" -eq 0 ]; then - refuse "skipped source '$source_name' carried package '$package' is missing from '$OUT_DIR'; build-deps must never fall back to stock bookworm" + refuse "skipped source '$source_name' carried package '$package' is missing from '$OUT_DIR'; build-deps must never fall back to the stock suite" fi if [ "${#matches[@]}" -gt 1 ]; then refuse "skipped source '$source_name' carried package '$package' has ${#matches[@]} staged debs in '$OUT_DIR'; the local repo seed requires exactly one version" diff --git a/packaging/ci/check-package-sets.sh b/packaging/ci/check-package-sets.sh index 3cbbdd9..5e62975 100755 --- a/packaging/ci/check-package-sets.sh +++ b/packaging/ci/check-package-sets.sh @@ -14,7 +14,7 @@ # names the exact offending package. # # USED BY -# build-bookworm.sh (in-container, after the runtime-closure check — fail-closed on drift) +# build-stack.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. # diff --git a/packaging/ci/check-upstream-freshness.sh b/packaging/ci/check-upstream-freshness.sh index d773d06..eac5b14 100755 --- a/packaging/ci/check-upstream-freshness.sh +++ b/packaging/ci/check-upstream-freshness.sh @@ -287,7 +287,7 @@ render_issue_body() { dispatches a build. Bumping a pin is a separate, human-reviewed change that must re-run `packaging/ci/verify-upstream-pins.sh` — the four-link provenance chain (lineage, `.dsc` authority, `.orig.tar` artifact, `debian/` packaging tree) — and refresh the checked-in - `debian/` recipes plus `packaging/BOOKWORM-ADAPTATIONS.md`. + `debian/` recipes plus `packaging/SUITE-ADAPTATIONS.md`. EOF diff --git a/packaging/ci/contract.sh b/packaging/ci/contract.sh index 6b27193..d64c0fa 100755 --- a/packaging/ci/contract.sh +++ b/packaging/ci/contract.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# contract.sh — the packaging PR lane (bookworm container, ci-packaging.yml). +# contract.sh — the packaging PR lane (target-suite container, ci-packaging.yml). # # This is the LIGHTWEIGHT gate that runs on every packaging PR without building any .deb: # it needs no docker-in-docker and no built artifacts. It asserts the packaging scaffold is @@ -8,7 +8,7 @@ # # The HEAVY, deb-consuming contract — metadata / closure install / upgrade / rollback / # coherence / piuparts and the daemon smoke — lives in ci/test-package-contract.sh and -# ci/daemon-smoke.sh. Those each launch their own debian:bookworm container against the +# ci/daemon-smoke.sh. Those each launch their own target-suite container against the # A5.1 build output and run inside release.yml's build-deb job (which has the host docker # daemon), not in this container-based PR lane. set -euo pipefail @@ -16,7 +16,7 @@ set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PKG_ROOT="$(cd "$HERE/.." && pwd)" -echo "packaging PR contract lane (bookworm)" +echo "packaging PR contract lane ($(. /etc/os-release 2>/dev/null && echo "${VERSION_CODENAME:-unknown-suite}"))" echo " packaging root: $PKG_ROOT" fail=0 @@ -30,7 +30,7 @@ require() { } require "README.md" -require "BOOKWORM-ADAPTATIONS.md" +require "SUITE-ADAPTATIONS.md" require "ci/tag-guard.sh" require "ci/test-tag-guard.sh" require "ci/detect-changed-sources.sh" @@ -39,8 +39,8 @@ require "ci/stage-carryforward-debs.sh" require "ci/test-stage-carryforward-debs.sh" require "ci/read-pin.sh" require "ci/inject-deb-version.sh" -require "ci/build-bookworm.sh" -require "ci/test-build-bookworm-differential.sh" +require "ci/build-stack.sh" +require "ci/test-build-stack-differential.sh" require "ci/test-package-contract.sh" require "ci/daemon-smoke.sh" require "ci/generate-release-manifest.sh" @@ -103,7 +103,7 @@ bash "$HERE/test-stage-carryforward-debs.sh" >/dev/null # carry seeding, bootstrap dispatch, counter derivation, package-set checking and merged closure all # run through their production paths, with no docker or network. echo " running differential build + rebuild-counter contract..." -bash "$HERE/test-build-bookworm-differential.sh" >/dev/null +bash "$HERE/test-build-stack-differential.sh" >/dev/null # The per-source suffix + mixed-version manifest contract. It stages placeholder debs and runs the # real generate-release-manifest.sh over them, and proves the migration-continuity ordering with @@ -114,8 +114,8 @@ bash "$HERE/test-suffix-coherence-manifest.sh" >/dev/null # The release.yml wiring proof is STATIC — it reads the workflow text and never dispatches a run. # It belongs in this lane because the invariant it guards (carry-forward staged before every -# build-bookworm.sh call) fails SILENTLY: a late stage still produces a green release, built -# against stock bookworm dependencies instead of the carried ones. +# build-stack.sh call) fails SILENTLY: a late stage still produces a green release, built +# against stock suite dependencies instead of the carried ones. echo " running release.yml differential wiring contract..." bash "$HERE/test-release-workflow-wiring.sh" >/dev/null @@ -162,10 +162,10 @@ if command -v dch >/dev/null 2>&1; then echo " ok: injection wrote to the copy; committed changelogs untouched" else # No devscripts here (e.g. a non-container run) — document instead of failing. - echo " note: dch not present; skipping live injection (runs in the bookworm PR lane)" + echo " note: dch not present; skipping live injection (runs in the containerized PR lane)" fi -# Real tilde-ordering proofs (dpkg is present in the bookworm lane). These are the invariant +# Real tilde-ordering proofs (dpkg is present in the container lane). These are the invariant # the encoded ~ceralive version depends on; a broken comparator would silently invert # release ordering. if command -v dpkg >/dev/null 2>&1; then diff --git a/packaging/ci/daemon-smoke.sh b/packaging/ci/daemon-smoke.sh index d0e313d..3668ebb 100755 --- a/packaging/ci/daemon-smoke.sh +++ b/packaging/ci/daemon-smoke.sh @@ -2,8 +2,8 @@ # 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 + -# NetworkManager (bookworm ships 1.42.4 — the plan's "NM 1.42") and the A5.1 build output, +# Inside a throwaway `debian:$TARGET_SUITE` container (default trixie) it installs system +# D-Bus + polkit + NetworkManager (trixie ships 1.52.1) and the A5.1 build output, # then: # * starts a system dbus-daemon and the ModemManager daemon on it; # * `busctl introspect` the MM service at its root path -> the ObjectManager interface; @@ -34,13 +34,17 @@ if [ "${IN_CONTAINER:-0}" != "1" ]; then PKG_ROOT="$(cd "$HERE/.." && pwd)" BUILD_DIR="${BUILD_DIR:-$PKG_ROOT/build/$ARCH}" + TARGET_SUITE="${TARGET_SUITE:-trixie}" + SMOKE_IMAGE="${SMOKE_IMAGE:-debian:$TARGET_SUITE}" + command -v docker >/dev/null 2>&1 || { echo "smoke: docker not found" >&2; exit 2; } ls "$BUILD_DIR"/*.deb >/dev/null 2>&1 || { - echo "smoke: no .deb in $BUILD_DIR — run ci/build-bookworm.sh $ARCH first" >&2; exit 2; } + echo "smoke: no .deb in $BUILD_DIR — run ci/build-stack.sh $ARCH first" >&2; exit 2; } echo "======================================================================" echo "daemon smoke arch=$ARCH" echo " build dir: $BUILD_DIR" + echo " image: $SMOKE_IMAGE" echo "======================================================================" # --privileged is NOT required: a plain container can run its own dbus-daemon + MM. @@ -48,7 +52,7 @@ if [ "${IN_CONTAINER:-0}" != "1" ]; then -e IN_CONTAINER=1 -e ARCH="$ARCH" \ -v "$PKG_ROOT":/pkg:ro \ -v "$BUILD_DIR":/debs:ro \ - debian:bookworm \ + "$SMOKE_IMAGE" \ bash /pkg/ci/daemon-smoke.sh "$ARCH" echo @@ -71,18 +75,18 @@ echo "== in-container daemon smoke (arch=$(dpkg --print-architecture), multiarch echo 'APT::Sandbox::User "root";' > /etc/apt/apt.conf.d/01-no-sandbox apt-get update -qq -# System bus (dbus), busctl (systemd), polkit, NetworkManager 1.42, mmcli deps, and dpkg-dev +# System bus (dbus), busctl (systemd), polkit, NetworkManager, mmcli deps, and dpkg-dev # (dpkg-scanpackages for the local repo). --no-install-recommends keeps NM from pulling stock # modemmanager back in as a recommend. -echo "-- installing system D-Bus + polkit + NetworkManager (bookworm 1.42) + tooling --" +echo "-- installing system D-Bus + polkit + NetworkManager + tooling --" apt-get install -y -qq --no-install-recommends \ - dbus systemd policykit-1 network-manager \ + dbus systemd polkitd network-manager \ dpkg-dev iproute2 udev ca-certificates >/tmp/base.log 2>&1 || { sed 's/^/ /' /tmp/base.log; echo "FAIL: base install"; exit 1; } NM_VER="$(dpkg-query -W -f='${Version}' network-manager 2>/dev/null || echo '?')" echo "-- NetworkManager installed: $NM_VER" -# Local repo of the built stack, pinned above bookworm-main (coherent ceralive set wins). +# Local repo of the built stack, pinned above the suite archive (coherent ceralive set wins). REPO=/tmp/localrepo rm -rf "$REPO"; mkdir -p "$REPO"; cp /debs/*.deb "$REPO/" ( cd "$REPO" && dpkg-scanpackages -m . /dev/null > Packages 2>/dev/null ) @@ -149,7 +153,7 @@ ls /usr/lib/udev/rules.d/77-mm-*.rules >/dev/null 2>&1 && ok "udev rules present [ -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 are FUNCTIONAL, not merely present ------------- -# A presence-only stat of the .typelib / .gir / .vapi would still pass if the GI-1.74 bookworm +# A presence-only stat of the .typelib / .gir / .vapi would still pass if the GI # 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 diff --git a/packaging/ci/detect-changed-sources.sh b/packaging/ci/detect-changed-sources.sh index 89284eb..1c34801 100755 --- a/packaging/ci/detect-changed-sources.sh +++ b/packaging/ci/detect-changed-sources.sh @@ -18,7 +18,7 @@ # # Source names are the upstream-pins.yaml PIN KEYS (lowercase `modemmanager`), not the # packaging directory names (`ModemManager`) — the mapping is NON-IDENTITY and is the same -# one build-bookworm.sh's `pin_key()` and read-pin.sh's `recipe_dir()` carry. Lines are +# one build-stack.sh's `pin_key()` and read-pin.sh's `recipe_dir()` carry. Lines are # emitted in BOOTSTRAP ORDER, so a consumer can build the selected set by reading top to # bottom. Every human-readable reason goes to STDERR, so stdout stays parseable. # @@ -30,7 +30,7 @@ # # FORCE-ALL (every source `changed`, `mode=force-all`, reason logged) # 1. A SHARED INPUT changed — `packaging/ci/**` (which subsumes `ci/expected-packages.txt`) -# or `packaging/BOOKWORM-ADAPTATIONS.md`. These feed every source's build, so a change to +# or `packaging/SUITE-ADAPTATIONS.md`. These feed every source's build, so a change to # one of them invalidates every carried artifact. # 2. The previous release is ABSENT, or carries no manifest asset. With no manifest there is # nothing to carry forward from, so rebuilding everything is the only honest answer. @@ -121,7 +121,7 @@ PKG_REL="${PKG_ROOT#"$REPO_ROOT"/}" [ "$PKG_REL" != "$PKG_ROOT" ] || die "packaging root '$PKG_ROOT' is not under repository root '$REPO_ROOT'" PINS_REL="$PKG_REL/upstream-pins.yaml" -# ---- contract constants (mirrored from build-bookworm.sh:41,46) ----------------------------- +# ---- contract constants (mirrored from build-stack.sh:41,46) ----------------------------- # Bootstrap order — the order a differential build must walk the selected set in. BUILD_ORDER=(libqrtr-glib libmbim libqmi ModemManager) # packaging dir -> upstream-pins.yaml source key. NON-IDENTITY: only ModemManager differs. @@ -161,7 +161,7 @@ fi is_shared_input() { case "$1" in "$PKG_REL"/ci/*) return 0 ;; - "$PKG_REL"/BOOKWORM-ADAPTATIONS.md) return 0 ;; + "$PKG_REL"/SUITE-ADAPTATIONS.md) return 0 ;; esac return 1 } @@ -274,7 +274,7 @@ resolve() { while IFS= read -r f; do [ -n "$f" ] || continue if is_shared_input "$f"; then - force "shared-input-changed — '$f' is a SHARED build input (packaging/ci/** or packaging/BOOKWORM-ADAPTATIONS.md); it feeds every source, so no source may be carried forward" + force "shared-input-changed — '$f' is a SHARED build input (packaging/ci/** or packaging/SUITE-ADAPTATIONS.md); it feeds every source, so no source may be carried forward" return fi done <<<"$CHANGED_FILES" diff --git a/packaging/ci/expected-packages.txt b/packaging/ci/expected-packages.txt index cda6219..9a52e7b 100644 --- a/packaging/ci/expected-packages.txt +++ b/packaging/ci/expected-packages.txt @@ -3,7 +3,7 @@ # 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: +# 1.38.0-1, libqrtr-glib 1.4.0-1), for the target-suite 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 @@ -20,7 +20,7 @@ # # 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 +# clean target-suite rebuild (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-* = diff --git a/packaging/ci/generate-release-manifest.sh b/packaging/ci/generate-release-manifest.sh index f97c970..52579dd 100755 --- a/packaging/ci/generate-release-manifest.sh +++ b/packaging/ci/generate-release-manifest.sh @@ -43,7 +43,7 @@ # (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. +# INPUT build-root (default: packaging/build) holding /*.deb from build-stack.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. # diff --git a/packaging/ci/read-pin.sh b/packaging/ci/read-pin.sh index 881e391..409a5b5 100755 --- a/packaging/ci/read-pin.sh +++ b/packaging/ci/read-pin.sh @@ -30,7 +30,7 @@ # # 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. +# Arch dev host and inside the packaging CI container alike. # # EXIT # 0 value printed on stdout. diff --git a/packaging/ci/stage-carryforward-debs.sh b/packaging/ci/stage-carryforward-debs.sh index b980f4f..b1fd9d9 100755 --- a/packaging/ci/stage-carryforward-debs.sh +++ b/packaging/ci/stage-carryforward-debs.sh @@ -7,9 +7,9 @@ # WHICH sources moved; this one carries the bytes of the ones that did not. Builds are NOT # reproducible, so an unchanged source can never be rebuilt at its old version — the only # honest way to keep a release self-contained is to reuse the exact artifacts the release that -# last built them recorded. Those bytes are a build INPUT too: `build-bookworm.sh` seeds its +# last built them recorded. Those bytes are a build INPUT too: `build-stack.sh` seeds its # temporary local apt repo from `build//`, so a changed source resolves its build-deps -# against the carried `-dev`/`gir1.2-*` packages rather than stock bookworm. +# against the carried `-dev`/`gir1.2-*` packages rather than the stock suite. # # INPUT — the verdict lines from detect-changed-sources.sh, on STDIN by default # The caller pipes that script's stdout straight in (or points `--verdicts ` at the file @@ -70,7 +70,7 @@ # # STAGING LAYOUT # `//` — the same gitignored tree -# `build-bookworm.sh` writes freshly built debs into and `generate-release-manifest.sh` reads. +# `build-stack.sh` writes freshly built debs into and `generate-release-manifest.sh` reads. # `build_arch` is taken verbatim from manifest column 1, so an `all` row would land in # `build/all/`. Only the companion is ever `Architecture: all`, and the companion is never # carried — an upstream source's rows are always arch-dependent — so `build/all/` should never diff --git a/packaging/ci/test-build-bookworm-differential.sh b/packaging/ci/test-build-stack-differential.sh similarity index 96% rename from packaging/ci/test-build-bookworm-differential.sh rename to packaging/ci/test-build-stack-differential.sh index a1f7500..1b6777f 100755 --- a/packaging/ci/test-build-bookworm-differential.sh +++ b/packaging/ci/test-build-stack-differential.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# test-build-bookworm-differential.sh — differential builder + rebuild-counter contract. +# test-build-stack-differential.sh — differential builder + rebuild-counter contract. # -# HOST-RUNNABLE, OFFLINE, NO DOCKER. The builder's BUILD_BOOKWORM_STUB_DIR seam replaces only +# HOST-RUNNABLE, OFFLINE, NO DOCKER. The builder's BUILD_STACK_STUB_DIR seam replaces only # the expensive source-build body with fixture artifacts; build-set parsing, carried-deb repo # seeding, bootstrap-order dispatch, the unchanged check-package-sets.sh call, and the merged # runtime-closure assertion are the production paths. A poisoned docker stub proves the @@ -13,7 +13,7 @@ set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BUILD_SCRIPT="$HERE/build-bookworm.sh" +BUILD_SCRIPT="$HERE/build-stack.sh" INJECT_SCRIPT="$HERE/inject-deb-version.sh" EXPECTED="$HERE/expected-packages.txt" @@ -256,8 +256,8 @@ EOF : >"$DCH_LOG" BUILD_IN_CONTAINER=1 ARCH=amd64 VERDICTS_FILE="$VERDICTS" RELEASE_VERSION=v1.2.0 \ - PREV_MANIFEST_FILE="$M_UNIFORM" BUILD_BOOKWORM_PKG_ROOT="$FIXTURE_PKG" \ - BUILD_BOOKWORM_OUT_DIR="$ONE_OUT" BUILD_BOOKWORM_STUB_DIR="$STUB_BUILDS" \ + PREV_MANIFEST_FILE="$M_UNIFORM" BUILD_STACK_PKG_ROOT="$FIXTURE_PKG" \ + BUILD_STACK_OUT_DIR="$ONE_OUT" BUILD_STACK_STUB_DIR="$STUB_BUILDS" \ PATH="$STUB_BIN:$PATH" DCH_LOG="$DCH_LOG" \ bash "$BUILD_SCRIPT" amd64 >"$TRACE" 2>&1 rc=$? @@ -321,5 +321,5 @@ if [ "$fail" -eq 0 ]; then echo "PASS: differential builds seed carried deps, preserve bootstrap order, and enforce coherent counters + merged closure" exit 0 fi -echo "FAIL: build-bookworm differential contract violated" +echo "FAIL: build-stack differential contract violated" exit 1 diff --git a/packaging/ci/test-detect-changed-sources.sh b/packaging/ci/test-detect-changed-sources.sh index d6b9445..6a21c27 100755 --- a/packaging/ci/test-detect-changed-sources.sh +++ b/packaging/ci/test-detect-changed-sources.sh @@ -67,8 +67,8 @@ chmod +x "$STUB_BIN/gh" mkdir -p "$REPO/packaging/ci" "$REPO/packaging/ceralive-modem-support/debian" "$REPO/control/src" cp "$SCRIPT" "$REPO/packaging/ci/detect-changed-sources.sh" cp "$REAL_PINS" "$REPO/packaging/upstream-pins.yaml" -printf 'shared build inputs live here\n' >"$REPO/packaging/BOOKWORM-ADAPTATIONS.md" -printf '# stand-in for the real builder\n' >"$REPO/packaging/ci/build-bookworm.sh" +printf 'shared build inputs live here\n' >"$REPO/packaging/SUITE-ADAPTATIONS.md" +printf '# stand-in for the real builder\n' >"$REPO/packaging/ci/build-stack.sh" printf '[libqmi all-artifact]\nlibqmi-glib5\n' >"$REPO/packaging/ci/expected-packages.txt" printf 'Source: ceralive-modem-support\n' >"$REPO/packaging/ceralive-modem-support/debian/control" printf 'export const x = 1;\n' >"$REPO/control/src/index.ts" @@ -176,7 +176,7 @@ if printf '%s\n' "$out" | grep -q '^ModemManager='; then bad "the directory name assert_has "libqmi=unchanged" "$out" "libqmi=unchanged" echo "== (c) a shared build input forces every source ==" -for shared in packaging/ci/build-bookworm.sh packaging/ci/expected-packages.txt packaging/BOOKWORM-ADAPTATIONS.md; do +for shared in packaging/ci/build-stack.sh packaging/ci/expected-packages.txt packaging/SUITE-ADAPTATIONS.md; do reset_repo printf '\n# shared input edit\n' >>"$REPO/$shared" commit_all "touch $shared" diff --git a/packaging/ci/test-package-contract.sh b/packaging/ci/test-package-contract.sh index eb47eca..749ef4f 100755 --- a/packaging/ci/test-package-contract.sh +++ b/packaging/ci/test-package-contract.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash -# test-package-contract.sh — the package contract suite for the bookworm +# test-package-contract.sh — the package contract suite for the target-suite # ModemManager 1.24 stack rebuilds. # # Runs the A5.1 build output (packaging/build//*.deb) through the contract every # device install must satisfy. All checks are REAL executed commands inside a throwaway -# `debian:bookworm` container — nothing is narrated. It NEVER mutates the committed +# `debian:$TARGET_SUITE` container (default trixie) — nothing is narrated. It NEVER mutates the committed # packaging tree (version-injection experiments operate on ephemeral strings / copies). # # CHECKS # 1 metadata/arch — Package/Version/Architecture over the 9-package runtime closure -# 2 closure install — clean bookworm: `apt-get install ./*.deb` of the 9, no missing deps -# 3 upgrade — stock modemmanager 1.20.4 -> the tag-encoded ceralive set +# 2 closure install — clean target suite: `apt-get install ./*.deb` of the 9, no missing deps +# 3 upgrade — stock modemmanager (suite archive) -> the ceralive set # 4 rollback — ceralive set -> stock, correct apt semantics (source-disable + # explicit stock versions + --allow-downgrades) # 5 coherence — PER-SOURCE: every runtime deb of ONE upstream source carries the @@ -34,10 +34,12 @@ # EXIT 0 all checks pass. 2 usage/env (no debs, no docker). non-zero = a contract breach. set -euo pipefail -# The 9-package runtime closure (contract constant, matches build-bookworm.sh). +# The 9-package runtime closure (contract constant, matches build-stack.sh). RUNTIME_PKGS=(modemmanager libmm-glib0 libmbim-glib4 libmbim-proxy libmbim-utils \ libqmi-glib5 libqmi-proxy libqmi-utils libqrtr-glib0) -STOCK_MM_UPSTREAM="1.20.4" # bookworm's stock modemmanager upstream version +STOCK_MM_UPSTREAM="${STOCK_MM_UPSTREAM:-1.24.0}" # the target suite's stock modemmanager upstream version +TARGET_SUITE="${TARGET_SUITE:-trixie}" +CONTRACT_IMAGE="${CONTRACT_IMAGE:-debian:$TARGET_SUITE}" # ========================================================================================== # HOST ROLE — tag-guard preamble (no container needed), then launch the container. @@ -57,12 +59,13 @@ if [ "${IN_CONTAINER:-0}" != "1" ]; then command -v docker >/dev/null 2>&1 || { echo "contract: docker not found" >&2; exit 2; } ls "$BUILD_DIR"/*.deb >/dev/null 2>&1 || { - echo "contract: no .deb in $BUILD_DIR — run ci/build-bookworm.sh $ARCH first" >&2; exit 2; } + echo "contract: no .deb in $BUILD_DIR — run ci/build-stack.sh $ARCH first" >&2; exit 2; } echo "======================================================================" echo "package contract suite arch=$ARCH mode=$MODE" echo " packaging root: $PKG_ROOT" echo " build dir: $BUILD_DIR" + echo " image: $CONTRACT_IMAGE" echo "======================================================================" # ---- CHECK 7 (host-side, before any container/deb work) ------------------------------ @@ -92,7 +95,7 @@ if [ "${IN_CONTAINER:-0}" != "1" ]; then -e STOCK_MM_UPSTREAM="$STOCK_MM_UPSTREAM" \ -v "$PKG_ROOT":/pkg:ro \ -v "$BUILD_DIR":/debs:ro \ - debian:bookworm \ + "$CONTRACT_IMAGE" \ bash /pkg/ci/test-package-contract.sh "$ARCH" echo @@ -103,7 +106,7 @@ if [ "${IN_CONTAINER:-0}" != "1" ]; then fi # ========================================================================================== -# CONTAINER ROLE — the real dpkg/apt checks, inside debian:bookworm. +# CONTAINER ROLE — the real dpkg/apt checks, inside the target-suite container. # ========================================================================================== ARCH="${ARCH:-$(dpkg --print-architecture)}" MODE="${CONTRACT_MODE:-full}" @@ -114,11 +117,11 @@ echo echo "== in-container contract (arch=$(dpkg --print-architecture), target=$ARCH, mode=$MODE) ==" # apt drops to the unprivileged _apt user for acquire and cannot read a local file: repo -# under a 0700 dir — same fix build-bookworm.sh uses. +# under a 0700 dir — same fix build-stack.sh uses. echo 'APT::Sandbox::User "root";' > /etc/apt/apt.conf.d/01-no-sandbox apt-get update -qq # dpkg-scanpackages (local-repo index for the upgrade/rollback scenarios) ships in dpkg-dev; -# base debian:bookworm has dpkg-deb/dpkg-query but not dpkg-dev. +# the base debian image has dpkg-deb/dpkg-query but not dpkg-dev. apt-get install -y -qq dpkg-dev >/dev/null 2>&1 # Resolve each runtime package name to its single .deb file (exact Package match — so @@ -155,8 +158,8 @@ 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 +# Expected per-package upgrade direction vs suite stock, EMPIRICALLY resolved for this bump +# (real `dpkg --compare-versions` in a target-suite container): 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. @@ -285,7 +288,7 @@ setup_local_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 at 1001 (> 1000) so the coherent ceralive set always - # wins regardless of direction. Every source now outranks bookworm-main on upstream version + # wins regardless of direction. Every source now outranks the suite archive 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' @@ -305,12 +308,12 @@ 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 +# Real per-package upgrade direction: each built ceralive deb vs suite 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) ----" + echo " ---- upgrade direction table (built ceralive deb vs suite 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 @@ -334,14 +337,14 @@ compute_direction_table() { } # ------------------------------------------------------------------------------------------ -# CHECK 2 — clean-bookworm dependency-closure install via `apt-get install ./*.deb`. +# CHECK 2 — clean-suite dependency-closure install via `apt-get install ./*.deb`. # ------------------------------------------------------------------------------------------ check_closure_install() { - echo; echo "==== CHECK 2: clean-bookworm dependency-closure install (apt-get install ./*.deb) ====" + echo; echo "==== CHECK 2: clean-suite dependency-closure install (apt-get install ./*.deb) ====" purge_stack local files=() for pkg in "${RUNTIME_PKGS[@]}"; do files+=("${DEB_OF[$pkg]}"); done - echo " installing the 9 runtime debs as local files (deps resolve from bookworm-main)..." + echo " installing the 9 runtime debs as local files (deps resolve from the suite archive)..." apt-get install -y -qq "${files[@]}" >/tmp/closure.log 2>&1 || { sed 's/^/ /' /tmp/closure.log; echo " CHECK 2 FAIL: install error"; return 1; } local pkg fail=0 for pkg in "${RUNTIME_PKGS[@]}"; do @@ -352,7 +355,7 @@ check_closure_install() { if ! apt-get check >/tmp/aptcheck.log 2>&1; then sed 's/^/ /' /tmp/aptcheck.log; echo " CHECK 2 FAIL: apt-get check reports broken deps"; return 1; fi [ "$fail" -eq 0 ] || { echo " CHECK 2 FAIL"; return 1; } echo " ok: apt-get check clean (no missing deps)" - echo " CHECK 2 PASS: the 9-package closure installs cleanly on stock bookworm." + echo " CHECK 2 PASS: the 9-package closure installs cleanly on the stock target suite." purge_stack } @@ -363,14 +366,14 @@ check_upgrade() { echo; echo "==== CHECK 3: upgrade (stock modemmanager ${STOCK_MM_UPSTREAM} -> ceralive set) ====" purge_stack disable_local_repo - echo " installing stock bookworm modemmanager + utils..." + echo " installing the suite's stock modemmanager + utils..." apt-get install -y -qq modemmanager libmbim-utils libqmi-utils >/tmp/stock.log 2>&1 || { sed 's/^/ /' /tmp/stock.log; echo " CHECK 3 FAIL: stock install"; return 1; } local before; before="$(dpkg_ver modemmanager)" 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 + case "$before" in ${STOCK_MM_UPSTREAM}*) echo " ok: stock is ${STOCK_MM_UPSTREAM}-series" ;; *) echo " note: suite 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. + # reports the true suite-archive 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..." @@ -410,13 +413,13 @@ check_rollback() { # view) NOT `apt-cache policy` Candidate — because apt refuses to auto-downgrade to a # version below priority 1000, Candidate keeps reporting the installed ceralive version. # madison lists only indexed versions, so once the local repo is gone it yields the real - # bookworm-main version (never hardcoded — point releases like +deb12u1 shift it). + # suite-archive version (never hardcoded — point releases like +deb13u1 shift it). echo " disabling local repo and pinning explicit stock versions..." disable_local_repo local specs=() pkg stock for pkg in "${RUNTIME_PKGS[@]}"; do stock="$(apt-cache madison "$pkg" 2>/dev/null | awk -F'|' 'NR==1{gsub(/^[ \t]+|[ \t]+$/,"",$2); print $2; exit}')" - [ -n "$stock" ] || { echo " note: $pkg has no bookworm stock version in the index — skipping"; continue; } + [ -n "$stock" ] || { echo " note: $pkg has no stock version in the suite index — skipping"; continue; } specs+=("${pkg}=${stock}") done echo " downgrading to: ${specs[*]}" @@ -424,7 +427,7 @@ check_rollback() { local after; after="$(dpkg_ver modemmanager)" echo " modemmanager after rollback: $after" case "$after" in *~ceralive*) echo " CHECK 4 FAIL: still on ceralive after rollback"; return 1 ;; ${STOCK_MM_UPSTREAM}*) echo " ok: back to stock ${STOCK_MM_UPSTREAM}" ;; *) echo " ok: back to stock $after" ;; esac - echo " CHECK 4 PASS: apt cleanly downgraded the stack back to stock bookworm." + echo " CHECK 4 PASS: apt cleanly downgraded the stack back to the stock target suite." purge_stack } @@ -433,7 +436,7 @@ check_rollback() { # ------------------------------------------------------------------------------------------ check_piuparts() { echo; echo "==== CHECK 8: piuparts-style install -> purge cleanliness ====" - echo " (lightweight install/purge/leftover-scan; real piuparts 1.1.7 exists in bookworm" + echo " (lightweight install/purge/leftover-scan; real piuparts exists in the suite" echo " but needs a privileged debootstrap chroot not available in this container.)" purge_stack local files=() diff --git a/packaging/ci/test-release-workflow-wiring.sh b/packaging/ci/test-release-workflow-wiring.sh index ae59711..ce8fdb4 100755 --- a/packaging/ci/test-release-workflow-wiring.sh +++ b/packaging/ci/test-release-workflow-wiring.sh @@ -7,14 +7,14 @@ # show you cheaply: a release costs two QEMU container builds, and the two failure modes this # guards against (staging after the build, and a `${{ }}` reaching a shell body) are both SILENT. # -# * ORDER — `stage-carryforward-debs.sh` must precede EVERY `build-bookworm.sh` invocation. -# Carried debs are a build INPUT: build-bookworm.sh seeds its Pin-Priority-1001 local apt repo +# * ORDER — `stage-carryforward-debs.sh` must precede EVERY `build-stack.sh` invocation. +# Carried debs are a build INPUT: build-stack.sh seeds its Pin-Priority-1001 local apt repo # from packaging/build//. Staged late, a changed source silently resolves its build-deps -# against stock bookworm and the release still goes green. +# against the stock suite and the release still goes green. # * ENV-ROUTING — no `${{ … }}` may be interpolated into a `run:` body (the file's own security # model, release.yml header). An interpolated tag is a shell-injection surface. # * ONE FETCH — the previous manifest is resolved once and reused by all three consumers. -# * NO WORKFLOW-LEVEL `if:` — conditional/zero-build selection lives INSIDE build-bookworm.sh. +# * NO WORKFLOW-LEVEL `if:` — conditional/zero-build selection lives INSIDE build-stack.sh. # A step-level `if:` would skip the companion build or the merged-closure assertion with it. # # Both non-trivial detectors carry a non-vacuity control, so a green run cannot come from a @@ -22,7 +22,7 @@ # # RESIDUAL RISK (accepted by the plan): the zero-build path reaching manifest generation with # staged-only debs is NOT proven end-to-end here or anywhere else — it is proven in pieces by -# test-build-bookworm-differential.sh and test-suffix-coherence-manifest.sh, and the full proof +# test-build-stack-differential.sh and test-suffix-coherence-manifest.sh, and the full proof # lands at the FIRST REAL RELEASE RUN. set -uo pipefail @@ -101,7 +101,7 @@ RESOLVE_LINE="$(line_of '- name: Resolve previous release + fetch its manifest ( DETECT_LINE="$(line_of '- name: Detect changed sources (per-source verdicts)')" STAGE_LINE="$(line_of '- name: Stage carry-forward debs (unchanged sources, sha256-verified)')" BUILD_STEP_LINE="$(line_of '- name: Build the MM 1.24 stack (.deb)')" -BUILD_CALL_LINE="$(line_of 'packaging/ci/build-bookworm.sh amd64')" +BUILD_CALL_LINE="$(line_of 'packaging/ci/build-stack.sh amd64')" COMPANION_LINE="$(line_of_invocation 'packaging/ci/build-companion\.sh')" MANIFEST_LINE="$(line_of 'run: bash packaging/ci/generate-release-manifest.sh')" UPLOAD_LINE="$(line_of '- name: Upload .deb artifacts + release manifest')" @@ -110,7 +110,7 @@ for pair in \ "resolve-previous-release:$RESOLVE_LINE" \ "detect-changed-sources:$DETECT_LINE" \ "stage-carryforward-debs:$STAGE_LINE" \ - "build-bookworm-invocation:$BUILD_CALL_LINE" \ + "build-stack-invocation:$BUILD_CALL_LINE" \ "build-companion:$COMPANION_LINE" \ "generate-release-manifest:$MANIFEST_LINE" \ "upload-artifacts:$UPLOAD_LINE"; do @@ -127,20 +127,20 @@ assert_before "Checkout the resolved commit" "$CHECKOUT_LINE" "Resolve previous assert_before "Resolve previous release" "$RESOLVE_LINE" "Detect changed sources" "$DETECT_LINE" assert_before "Detect changed sources" "$DETECT_LINE" "Stage carry-forward debs" "$STAGE_LINE" assert_before "Stage carry-forward debs" "$STAGE_LINE" "Build the MM 1.24 stack" "$BUILD_STEP_LINE" -assert_before "Stage carry-forward debs" "$STAGE_LINE" "build-bookworm.sh invocation" "$BUILD_CALL_LINE" -assert_before "build-bookworm.sh invocation" "$BUILD_CALL_LINE" "companion build" "$COMPANION_LINE" +assert_before "Stage carry-forward debs" "$STAGE_LINE" "build-stack.sh invocation" "$BUILD_CALL_LINE" +assert_before "build-stack.sh invocation" "$BUILD_CALL_LINE" "companion build" "$COMPANION_LINE" assert_before "companion build" "$COMPANION_LINE" "generate-release-manifest.sh" "$MANIFEST_LINE" assert_before "generate-release-manifest.sh" "$MANIFEST_LINE" "artifact upload" "$UPLOAD_LINE" # The headline invariant, stated over EVERY invocation rather than the first. Comment lines are -# excluded because release.yml discusses build-bookworm.sh in prose above the job's first step; +# excluded because release.yml discusses build-stack.sh in prose above the job's first step; # only an executable mention is an invocation. earliest_build="" while IFS=: read -r n _; do [ -n "$n" ] || continue if [ -z "$earliest_build" ] || [ "$n" -lt "$earliest_build" ]; then earliest_build="$n"; fi -done < <(grep -n -F 'build-bookworm.sh' "$WORKFLOW" | grep -vE '^[0-9]+:[[:space:]]*#') -assert_before "Stage carry-forward debs" "$STAGE_LINE" "EARLIEST executable build-bookworm.sh mention" "$earliest_build" +done < <(grep -n -F 'build-stack.sh' "$WORKFLOW" | grep -vE '^[0-9]+:[[:space:]]*#') +assert_before "Stage carry-forward debs" "$STAGE_LINE" "EARLIEST executable build-stack.sh mention" "$earliest_build" # Non-vacuity: the comparator must reject an inverted pair. The operands are SYNTHETIC constants, # not line numbers read from the file, so this control keeps its meaning even when it is run @@ -239,16 +239,16 @@ fi if grep -qE '^\s+VERDICTS_FILE: verdicts\.txt$' "$WORKFLOW"; then ok "the builder receives an EXPLICIT build set (VERDICTS_FILE) — never the build-all local-dev default" else - bad "the build step does not supply VERDICTS_FILE; CI would fall back to build-bookworm.sh's build-all default" + bad "the build step does not supply VERDICTS_FILE; CI would fall back to build-stack.sh's build-all default" fi # ---- 7. no workflow-level `if:` in build-deb --------------------------------------------------- -# Zero-build selection is build-bookworm.sh's job. A step-level `if:` here would also skip the +# Zero-build selection is build-stack.sh's job. A step-level `if:` here would also skip the # always-rebuilt companion and the merged-closure assertion, which must run on every release. if [ -n "$build_deb_start" ] && [ -n "$publish_npm_start" ]; then if sed -n "${build_deb_start},${publish_npm_start}p" "$WORKFLOW" | grep -qE '^\s+if:'; then - bad "build-deb carries a step/job-level 'if:' — conditionality belongs inside build-bookworm.sh" + bad "build-deb carries a step/job-level 'if:' — conditionality belongs inside build-stack.sh" else ok "build-deb has no workflow-level 'if:' (the companion build stays unconditional)" fi diff --git a/packaging/upstream-pins.yaml b/packaging/upstream-pins.yaml index 294fb11..c179606 100644 --- a/packaging/upstream-pins.yaml +++ b/packaging/upstream-pins.yaml @@ -1,7 +1,7 @@ # upstream-pins.yaml — provenance-verified pin manifest for the ModemManager stack. # # WHAT THIS IS -# The four upstream sources CeraLive rebuilds for bookworm, each pinned to an EXACT +# The four upstream sources CeraLive rebuilds for the target suite (trixie), each pinned to an EXACT # upstream release tag and its matching Debian packaging (salsa) tag. Every field is # 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.