Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci-packaging.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand All @@ -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
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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/<arch>/, 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:
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
44 changes: 30 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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/<Source>/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 <amd64|arm64>` 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 <amd64|arm64>` (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/<arch>/`.

**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
Expand All @@ -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**
Expand All @@ -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
Expand All @@ -1697,18 +1713,18 @@ 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/<arch>/`, 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
over the carried set.

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
Expand Down
5 changes: 3 additions & 2 deletions POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -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).",
Expand Down
2 changes: 1 addition & 1 deletion cli/smoke/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion control/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading