DEV Community

arcker
arcker

Posted on Originally published at arcker.org

Eighteen hours to close the loop

Lithair is a memory-first Rust web framework I build; cidx is the declarative CI runner I build, and it runs Lithair's CI. Both are open source. This post stands on its own.

A brand-new container in Lithair's CI, declared in four lines of cidx.toml: a Rust image pinned by digest, a script that installs PostgreSQL 17 inside it, generates a test-only PKI and runs the suite with LITHAIR_REQUIRE_POSTGRES=1, and privileged = true, because installing a package means writing to /var/lib/apt.

And it falls over:

E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied)
Enter fullscreen mode Exit fullscreen mode

As if the flag did not exist. privileged = true is right there, read three times. The container still runs as the host user.

What was happening

cidx reuses containers from one run to the next. To decide whether the existing container is still the right one, it hashes the configuration: image, command, working directory, entrypoint, volumes, environment variables. And a comment in the code, an honest one, says that PullPolicy, Privileged and Timeout are deliberately left out of the hash because they "affect execution behavior, not the container".

True for the timeout. False for privileged. In cidx an unprivileged container runs with the host user's id; a privileged one runs as root. The flag therefore changes User in the Docker config, which is the container itself. Flipping it from false to true, cidx looked at the hash, found it unchanged, and picked up the container created earlier, with its earlier user.

docker rm -f on the container clears it. That is not a fix, it is a diagnosis: issue cidx#531 goes up with the cause and a proposal, include in the hash everything that changes the created container's configuration.

Eighteen hours

  2026-10-02 14:55Z   lithair      cidx#531 opened: "privileged reuses the previous container"
  2026-10-03 08:04Z   cidx         #532  recreate the container when the user it runs as changes
             08:27Z   cidx         #533  compare the environment the container was created with, not the declared one
             08:39Z   cidx         #534  v3.8.0
             09:07Z   lithair      #302  "ci: upgrade cidx to 3.8.0" — the only diff is the number
Enter fullscreen mode Exit fullscreen mode

Eighteen hours and twelve minutes from the issue to the consumed release. The second fix was not in the issue: looking at the hash surfaced a neighbouring defect. cidx compared the environment declared in the config, not the one the container had been created with. Two paths to the same question, "is this container still the right one?", and one of them could say yes wrongly.

The Lithair PR that consumes the release has a one-sentence description: "3.8.0 recreates a container when privileged changes, found while adding the postgres-test container". The GitHub workflow is regenerated by cidx generate github, and the only diff is the version pin.

Not the first time that week

It was the fourth time in five days that Lithair's CI wrote cidx's requirements.

  • No cache in the generated workflow (cidx#503): v3.5.0 adds a per-phase cache, Lithair consumes it as 3.6.0 on September 29th.
  • No cancellation of previous runs on a PR (cidx#504): same release.
  • The cache restores from the previous key when the key changes (cidx#515). Measured on Lithair's test phase: cargo never prunes target/, so every key change stacked a generation of artifacts on the previous one, 3.3 → 5.4 GiB after a single version bump. Fixed in 3.7.0 with cache_restore_fallback = false; the first run after a key change compiles cold, which is the honest price of a cache that does not lie about its size.
  • privileged ignored (cidx#531): 3.8.0.

Three cidx bumps in Lithair in five days, 3.6.0, 3.7.0, 3.8.0, each with its reason written in the PR. Seven issues opened by the same author and closed the same week on the cidx side. It is the same mechanism that produced seven Lithair releases in August: the consumer dictates, the tool follows. This time Lithair is the consumer and cidx is the tool.

Meanwhile, Lithair

The Postgres container existed because Lithair had just gained its third storage tier. RFC 296 sets the vocabulary before the code:

  tier     where                        today
  ─────────────────────────────────────────────────────────────────
  L1       process memory               native models (SCC2), always resident
  L2       local durable storage        native log + snapshots; embedded Turso
  L3       external database            new: PostgreSQL

  a model's authority lives in exactly one tier;
  "durable" is declared, and requires an L3 authority
Enter fullscreen mode Exit fullscreen mode

lithair-postgres 0.1 lands in v1.17.0 with the same surface as the Turso adapter: #[storage(postgres)], the same generated routes, migrations and application commands. The choices are those of someone who has already lost data: SERIALIZABLE everywhere with a bounded number of retries, a lithair schema the framework owns and migrates under an advisory lock so it happens exactly once across nodes, TLS verified whatever sslmode asks for, Unix sockets refused. The core still depends on no driver.

The rest of the week, in order: v1.13.0 wires declarative models onto the native consensus and fixes the frontend journal that had bloated this very site to 1.3 GB; v1.14.0 and v1.15.0 add atomic application commands, native then Turso; v1.16.0 brings OpenID Connect login for custom handlers, with PKCE and single-use login attempts; v1.18.0 puts an L1 cache on external models, with #[retention(memory, max_mb, ttl)] and invalidation through pg_notify. Three RFCs merged before their code, every time.

The limit, named

Seven releases in seven days is a sprint, not a cadence. I wrote the same sentence two weeks ago about five releases in two days; it still holds.

And a mistake I caught this morning in my own tooling: session replication in the native cluster was merged five hours after the v1.18.0 tag. It is on main, it is in no release, and the automated tracking I maintain had filed it under 1.18. The next release will make it true; the map was wrong until this morning.

Finally, OIDC login pulls rsa 0.9 through openidconnect, and that crate carries RUSTSEC-2023-0071. It is written in the release notes, not hidden in an exceptions file. And this is where the cidx week meets the Lithair week: since 3.7.0, cidx's audit gate gives a finding seven days from first sight before it fails the build, counted from when the scanner first saw it rather than from the advisory's publication date, with no grace for a known-exploited vulnerability or an expired exception. The same week, three CVEs published on the day got their answer on the day. A gate that leaves seven days to decide and then breaks is the only way two repositories can ship on the same morning without lying about a same-day CVE.

A loop that closes in eighteen hours is not speed. It is an ecosystem that exists.

French original on arcker.org.

Top comments (0)