Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
10b6b15
packaging: bump pins to MM 1.24.2 / libmbim 1.34.0 / libqmi 1.38.0 / …
andrescera Jul 17, 2026
737d912
packaging: refresh debian/ trees; bookworm adaptations incl. GI-1.74;…
andrescera Jul 17, 2026
8fea233
packaging(ci): shared pin reader; revision-exact, direction-aware ass…
andrescera Jul 17, 2026
aa755ec
packaging(ci): enforce exact per-source package sets
andrescera Jul 17, 2026
6432407
packaging(ci): functional GI validation in daemon smoke
andrescera Jul 17, 2026
f51118c
chore(deps): biome 2.5.4; dependabot policy: no TS major bumps
andrescera Jul 17, 2026
6de9d03
test(transport): characterization coverage for reconnect/ordering edges
andrescera Jul 17, 2026
d75863f
test(ports): AST-based forbidden-surface detector (compiler API), neg…
andrescera Jul 17, 2026
3181929
docs(fm350): tracked decision record (PCIe, deferred); parse 1.24.2 v…
andrescera Jul 17, 2026
29d9f70
ci(release): peeled-SHA pinning, env-quarantined inputs, build-before…
andrescera Jul 17, 2026
da0e37f
refactor(transport): split transport.ts into lifecycle/calls/signals …
andrescera Jul 17, 2026
9c47823
merge: wave2 2.1 release.yml hardening
andrescera Jul 17, 2026
e0ff727
merge: wave2 2.2 dependabot + biome
andrescera Jul 17, 2026
1ea0c12
merge: wave2 2.3 transport characterization + split
andrescera Jul 17, 2026
f121557
merge: wave2 2.4 AST forbidden-surface detector
andrescera Jul 17, 2026
d72d793
merge: wave2 2.5 FM350 decision doc
andrescera Jul 17, 2026
b12a5ac
chore(biome): sync schema URL to 2.5.4
andrescera Jul 17, 2026
1d3b805
docs: version truth sweep for the 1.24.2/1.34.0/1.38.0/1.4.0 stack
andrescera Jul 17, 2026
f01b8e4
test(transport): cancel abandoned fake reply timers on bus drop
andrescera Jul 17, 2026
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
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ updates:
groups:
npm:
patterns: ["*"]
# TypeScript 7 is explicitly out of scope; never propose a semver-major TS bump.
ignore:
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
395 changes: 328 additions & 67 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

67 changes: 57 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,43 @@ npm and the `.deb` set. `.deb` versions encode the tag as `<upstream>-<rev>~cera
(upstream-ordered, apt-safe; injected with `dch --force-bad-version`). Non-tag CI builds use
`~ceralive0.0.0~dev`. Full contract: `docs/VERSIONING.md`.

## PROVENANCE PINS (packaging)

The four rebuilt sources are pinned in `packaging/upstream-pins.yaml`, re-verified end-to-end
by `packaging/ci/verify-upstream-pins.sh` in an isolated `GNUPGHOME`. Current pins:

| Source | Upstream | Salsa packaging tag |
|--------|----------|---------------------|
| ModemManager | 1.24.2 | `debian/1.24.2-2` |
| libmbim | 1.34.0 | `debian/1.34.0-1` |
| libqmi | 1.38.0 | `debian/1.38.0-1` |
| libqrtr-glib | 1.4.0 | `debian/1.4.0-1` |

Each pin carries a **four-link provenance chain**, all re-checked and failing closed with a
named field on any drift:

1. **Lineage** — the upstream git tag object + peeled commit SHA (`git ls-remote`; the tag is
never byte-compared to a git archive).
2. **Authority** — the signed Debian `.dsc`, GPG-verified against a pinned signer fingerprint
whose armored key lives in `packaging/keys/` (mapping in `packaging/keys/README.md`).
3. **Artifact** — the `.orig.tar`, whose sha256 equals the verified `.dsc`'s
`Checksums-Sha256` entry.
4. **Packaging** — the `.debian.tar.xz`, whose sha256 equals the `.dsc`, and whose extracted
`debian/` tree is proven byte-identical to the pinned salsa tag via a canonical metadata
manifest (path, file type, exec-bit, symlink target, content sha256 per entry).

The container build additionally enforces the finalized **two-set package model** (declared
arch-dependent stanzas + enumerated `-dbgsym`) for exact per-source set **equality** via
`packaging/ci/check-package-sets.sh` (add/remove/rename fails closed). Full detail:
`packaging/README.md`.

## POLICY

`packaging/` is a **no-fork** effort: the first release carries zero quilt patches; adding a
patch later is an architecture gate (rationale + filed upstream MR + review);
udev/plugin/device-support improvements go **upstream first**. Full terms: `POLICY.md`.
The Fibocom **FM350** modem (PCIe / `mtk_t7xx`) is documented-**deferred**, not supported —
rationale, source cites, and the open gates are recorded in `docs/FM350-DECISION.md`.

## WORKSPACE / TOOLCHAIN

Expand Down Expand Up @@ -90,18 +122,33 @@ major action versions, per-manager caches, weekly grouped Dependabot, test-befor
closure / upgrade / rollback / daemon smoke) lands in a later task.
`cancel-in-progress: true`.
- **`.github/workflows/release.yml`** — the **single** release workflow, owns **both**
artifacts. `workflow_dispatch` with a `tag` input. Job graph:
1. **tag-guard** (`packaging/ci/tag-guard.sh`) — input must match `^v\d+\.\d+\.\d+$`;
anything else (pre-release, build metadata, missing `v`) **fails closed** before any
other job runs.
artifacts. `workflow_dispatch` with a `tag` input. **Strictly sequential** job graph
`tag-guard → test → build-deb → publish-npm → create-release` (build-before-publish; npm
never publishes before the `.deb` set builds green). Every downstream job checks out
`ref: needs.tag-guard.outputs.sha` and re-asserts `git rev-parse HEAD` equals that peeled
SHA; every dynamic input is routed through `env:` (no `${{ }}` in any `run:` body):
1. **tag-guard** — resolves the tag to its **peeled commit SHA**
(`packaging/ci/resolve-tag.sh`, `git ls-remote`, prefers `refs/tags/<tag>^{}`),
re-checks out that SHA, asserts HEAD, then runs `packaging/ci/tag-guard.sh` (input must
match `^v\d+\.\d+\.\d+$`; pre-release / build-metadata / missing-`v` **fails closed**
before any other job). Exports `version` + `sha`.
2. **test** (needs tag-guard) — full bun lane + packaging contract lane
(test-before-publish).
3. **publish-npm** (needs test) — OIDC trusted publishing (`id-token: write`), verifies
`control/package.json` version === tag, `npm publish --access public`.
4. **build-deb** (needs test) — strips `v`, injects `<upstream>-<rev>~ceralive<X.Y.Z>`
into each source's `debian/changelog` via `dch --force-bad-version`
(`packaging/ci/inject-deb-version.sh`), uploads `.deb` artifacts + a release manifest.
Non-tag runs use `~ceralive0.0.0~dev`.
3. **build-deb** (needs [tag-guard, test]) — injects `<upstream>-<rev>~ceralive<X.Y.Z>`
(non-tag runs `~ceralive0.0.0~dev`) via `packaging/ci/inject-deb-version.sh`, builds both
arches, runs the package contract suite + daemon smoke, generates the manifest-complete
release manifest (`packaging/ci/generate-release-manifest.sh`), and uploads the `.deb`
artifacts + manifest.
4. **publish-npm** (needs [tag-guard, build-deb]) — OIDC trusted publishing
(`id-token: write`), verifies `control/package.json` version === tag, then an
**integrity-idempotent** publish: `npm pack` → classify registry state (404 → publish;
present+matching integrity → idempotent skip; present+differing → fail closed), with a
last-instant `resolve-tag.sh` re-verification immediately before `npm publish`.
5. **create-release** (needs [tag-guard, build-deb, publish-npm], `contents: write`) —
pre-create moved-tag re-check, downloads the `.deb` + manifest artifact, assembles a flat
asset dir, and reconciles it immutably via `packaging/ci/reconcile-release-assets.sh`
(manifest-complete, staged sanitized `~`→`.` names, collision-rejected, existing assets
integrity-compared and never overwritten).
`cancel-in-progress: false` (never cancel a release/publish mid-run).

Action pins track the latest stable **major** (resolved via the `gh api` releases/latest
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ changes to CeraUI, the device image, or apt-worker**. Bench devices install the

## Versioning at a glance

ONE unified **SemVer** tag `vX.Y.Z` releases **both** artifacts together: `v0.1.0` publishes
`@ceralive/modem-control@0.1.0` to npm **and** the `.deb` artifact set in the same release.
ONE unified **SemVer** tag `vX.Y.Z` releases **both** artifacts together: `v0.2.0` publishes
`@ceralive/modem-control@0.2.0` to npm **and** the `.deb` artifact set in the same release.
This repo deliberately does **not** use the CeraLive CalVer scheme. The `.deb` internal
`Version:` fields encode the tag as `<upstream>-<rev>~ceralive<X.Y.Z>` (e.g.
`1.24.0-1~ceralive0.1.0`) so apt ordering stays correct. Full contract:
`1.24.2-2~ceralive0.2.0`) so apt ordering stays correct. Full contract:
[`docs/VERSIONING.md`](docs/VERSIONING.md).

## Layout
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.4/schema.json",
"extends": ["@ceralive/biome-config"],
"files": {
"includes": ["**", "!**/node_modules", "!**/dist", "!test-results"]
Expand Down
20 changes: 10 additions & 10 deletions bun.lock

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

8 changes: 8 additions & 0 deletions control/src/backend/device-classifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
// `mm-managed`; a bare vendor-specific interface with no recognized driver is NOT a
// modem. `pending-modeswitch` is a DISTINCT state (a modem installer awaiting
// `usb_modeswitch`), never conflated with `unmanaged`.
//
// SCOPE — USB ONLY: the whole input here is a `UsbDeviceSnapshot`, a udev/sysfs view of a
// USB device. PCIe modems are out of scope by construction and get NO entry in this model —
// a PCI `vendor:device` pair is never smuggled in as a pseudo-USB identity. The Fibocom
// FM350 is the canonical example: it is a PCIe module (PCI `14c3:4d75`, bound by the
// `mtk_t7xx` driver on the `wwan`/`net` subsystems, with no USB VID:PID), so it is
// documented-deferred rather than classified here. See `docs/FM350-DECISION.md` for the
// evidence and the three-gate ledger behind that decision.

import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode';

Expand Down
4 changes: 4 additions & 0 deletions control/src/backend/features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ describe('parseMmVersion', () => {
expect(parseMmVersion('1.24.0')).toEqual({ major: 1, minor: 24 });
});

test('parses the 1.24.2 FM350-fix release', () => {
expect(parseMmVersion('1.24.2')).toEqual({ major: 1, minor: 24 });
});

test('parses major.minor without a patch', () => {
expect(parseMmVersion('1.20')).toEqual({ major: 1, minor: 20 });
});
Expand Down
Loading