← Journal

I Picked Up a FireCrab License Issue.
It Turned Into a Release Compliance System.

Issue #176 looked like release cleanup. PR #190 turned it into a system for proving what FireCrab ships, what legal evidence travels with it, and where the exact corresponding source lives. Then upstream merged it — and started building the next layer on top.


A warm release-engineering desk with FireCrab branding, issue 176 becoming pull request 190, distro validation, and an all-green release build.
The #176 proof path in one frame: package evidence, exact source, release validation, and the upstream PR. Editorial illustration.
#176 The issuerelease compliance audit ↗ #190 Merged upstreamAugust 27, 2026 ↗ #210 The next layerOCI-import SPDX SBOMs ↗

The issue was titled “fix(release): add license compliance artifacts.” That sounds small. It was not.

FireCrab is a self-hosted microVM cloud built around AWS Firecracker. Its release process ships host binaries, a frontend, installer helpers, Firecracker tooling, and M2Image guest operating systems. Once you look at that as a distribution event instead of merely a build event, the compliance surface gets big very quickly.

#176 called out missing host notices, a GPL-covered helper, incomplete transitive attribution, Firecracker notice preservation, per-image SBOMs, real kernel/package versions, and matching source for GPL-covered guest software. The first mistake would have been treating that list as a pile of files to copy.

Rendered capture of FireCrab issue 176 and its release-compliance requirements.
Rendered from the scope of FireCrab issue #176.

The patch needed a release contract

A weak fix would have copied LICENSE into a tarball, generated a notice file, and stopped there. That would have addressed the visible symptom while leaving the real problem intact: nothing connected the thing being distributed to the evidence required to distribute it responsibly.

So the host side became a release-time inventory. Cargo and frontend runtime dependencies were classified, build/test-only packages were separated, notices were generated mechanically, and missing or clearly incompatible runtime declarations became a hard failure instead of documentation debt.

The host bundle itself changed meaning. It now had a defined compliance payload: FireCrab’s license, generated third-party notices, a machine-readable inventory, the GPL text required by the release tooling, and preserved upstream Firecracker notices.

Release contract
01

Observe

Read real lockfiles, package databases, and built guest images.

02

Describe

Produce transitive attribution, SPDX, and package→source identity.

03

Package

Put legal evidence and a frozen source plan inside the artifact.

04

Materialize

Retrieve exact corresponding source and hash the bytes.

05

Publish

Upload source first; make the catalog visible last.

The guest images made it a supply-chain problem

FireCrab’s M2Images contain complete operating-system package sets: Alpine, Ubuntu, and Rocky. Kernels, BusyBox, libc, networking tools, package-manager components, and a long tail of libraries all become part of the shipped artifact. A repository-root notice cannot tell you what a particular image actually contains.

The build therefore started producing SPDX 2.3 from the package database inside the built image itself. Every installed binary package gets a source identity or an explicit narrow non-source disposition. The source plan is frozen into the artifact so later publication cannot silently reinterpret what was built.

That immediately found real gaps. The first Alpine proof had zero guest legal files. The answer was not to weaken the check; the image gained Alpine’s packaged SPDX license-text corpus. Rocky found a different weakness: an early validator encoded package counts as constants. When the repository moved, the validator failed even though the implementation was correct. The magic numbers were replaced with structural invariants.

The unit of work was no longer the patch. It was the proof.

Phase Blaster: make the validator hostile

I did not want the project’s own CI to be the only place where the logic was invented, exercised, and declared correct. I wanted an external validation plane whose job was to attack the assumptions from outside the normal release path.

I called that harness the Phase Blaster. It ran the policy tests in a disposable Python container and treated the release contract adversarially. Complete bundles had to pass. Zero legal evidence, missing source identity, malformed package metadata, the wrong SPDX version, and tampered indexed license bytes had to fail. Deterministic rebuilds had to remain deterministic.

Terminal-style rendered capture of Phase Blaster adversarial validation and exact Alpine, Ubuntu, and Rocky source materialization.
Phase Blaster evidence capture: policy rejection cases plus real-distro source publication. The Rocky proof reached 192 installed packages mapped to 132 exact source units.

The useful thing about the Phase Blaster was that it kept finding work. Alpine source recipes needed a writable scratch workspace while the pristine recipe remained evidence. Ubuntu retrieval had to preserve exact source versions. Rocky needed bounded concurrent SRPM fetching instead of a snapshot-specific count. Each failure made the release contract stronger.

“Source available somewhere” was not enough

Provenance can tell you where a package came from. That is useful, but it is not the same thing as publishing the matching source next to the binary artifact. So the M2Image bundle freezes a package→source publication plan at build time.

A source materializer consumes that plan and retrieves the exact units required by the built image: Alpine recipe state and distfiles, exact-version Ubuntu source packages, and Rocky source RPMs. The resulting .sources.tar.zst is indexed and hashed. The release catalog binds binary and source objects independently by SHA-256 and size.

Publication ordering is fail-closed: source is uploaded first, the binary follows, and catalog.json becomes visible last. A consumer cannot discover a new M2Image through the catalog while its source sibling is missing.

The invariant

No discoverable M2Image release without the exact source artifact required by the frozen build plan.

Then the proof moved into FireCrab’s own release path

An external validator is useful, but a compliance subsystem that works only in a special harness is still optional. The final pass wired the SBOM, legal-bundle, source-package, tamper, and publication contracts into FireCrab’s native Release Build compliance job.

Rendered capture showing final FireCrab CI and Release Build completed successfully.
Final fork validation at ef0002b: normal CI and the complete Release Build both passed, including the release-compliance contracts and host archive checks.
4,367lines added
31files changed
61commits
132exact Rocky source units in the full proof

The submitted SHA passed formatting, Clippy, tests and coverage, rustdoc, frontend, installer integration, distro dependency smoke, x86_64 and aarch64 builds across GNU and musl, all four host bundles, archive-member verification, and runtime smoke. Only then did I open the upstream pull request.

Rendered capture of SteelCrab FireCrab pull request 190 with 4,367 additions across 31 files.
SteelCrab/firecrab #190, opened after the exact submitted SHA had already passed the full fork validation path.

Then upstream answered

PR #190 merged into FireCrab on August 27. Before merging it, the maintainer created follow-up issues #199, #200, and #207 rather than treating the compliance work as finished. That is the part I care about most: the patch did not land as a one-off pile of legal artifacts. It became a seam the project could keep extending.

There was another small signal I want to preserve exactly because it belongs to the project’s own voice. After the Clippy warning gate landed in #189, the maintainer wrote: “Happy Crab! 🦀” I am leaving the phrase as-is. Open source has enough flattened corporate language already.

What happened next
#190

Merged

Release compliance became upstream behavior, not an external proposal.

#211

Baseline

The Clippy warning baseline refresh merged cleanly after warnings fell from 53 to 45.

#210

OCI evidence

Arbitrary OCI imports now have an open path toward SPDX 2.3 generated from apk, dpkg, or RPM state.

#209

Boot proof

The open M2Image CI fix drives the real MicroRegistry install path and has passed six of six guest boots.

#197

Local trust

Host-local MicroRegistry packages are being wired into the same checksum-before-trust model.

#210 is the clearest continuation of the original idea. Instead of limiting evidence generation to curated M2Images, it moves the boundary to arbitrary OCI imports: inspect the merged root filesystem before FireCrab mutates it, detect apk/dpkg/RPM package databases as data, generate SPDX 2.3, and persist the result under a stable compliance path. It also refuses to follow symlinked package-database paths outside the imported rootfs and keeps unknown or distroless images usable by making unsupported package managers warning-only rather than turning evidence collection into a false compatibility gate.

#209 attacks a different class of drift. CI had stopped exercising the actual M2Image distribution path and could report success around alias collisions. The proposed fix drives the canonical two-phase MicroRegistry package/install flow and verifies that the requested image really became installed. Its forced validation passed six of six boots across two Ubuntu hosts and Alpine, Ubuntu, and Rocky guests.

#197 takes the same instinct into host-local images: verify the staged package SHA-256 before trusting embedded metadata, then require the template identity to agree with the local catalog row before handing the image to the existing installer. Different feature, same underlying rule: do not trust a name when you can bind the thing to evidence.

The release ticket did not end at merge. It taught the project where to put the next proof.

The interesting part is not who typed 4,367 lines

Implementation leverage changes what one person can attempt, but it does not remove the judgment problem. It makes the judgment problem more important. What is the invariant? Which failure is meaningful? Which green check is ceremonial? When is a test protecting a policy, and when is it merely memorizing yesterday’s package count?

The Phase Blaster made that separation useful. Implementation could move quickly while the validator stayed hostile. The result was not merely more code. It was a chain of evidence that kept forcing the implementation to become more precise.

One issue became an operating model

The release model is simple to say even though it took a lot of machinery to make real: observe the actual distribution → generate evidence → freeze source identity → materialize exact source → hash it → publish source first → expose the catalog last.

What changed after merge is that this stopped being only the story of #176 and #190. The same pattern is now showing up in OCI import, MicroRegistry installation, CI boot verification, and local package trust. That is a much stronger outcome than getting one large PR accepted. The architecture is reproducing itself.

A boring-looking release ticket hid a real architecture problem. The useful move was to define the proof before declaring the patch done — and then leave enough structure behind that the next proof had somewhere obvious to go.


This is a technical field note, not legal advice. The mechanical gates and source-publication machinery are engineering controls; license sufficiency ultimately depends on the applicable licenses and distribution facts.