Skip to content

feat: publish libcpex_ffi.a as signed release artifacts#59

Merged
terylt merged 4 commits into
feat/apl_rustfrom
feat/ffi_prebuilt_a
Jun 3, 2026
Merged

feat: publish libcpex_ffi.a as signed release artifacts#59
terylt merged 4 commits into
feat/apl_rustfrom
feat/ffi_prebuilt_a

Conversation

@araujof
Copy link
Copy Markdown
Contributor

@araujof araujof commented Jun 3, 2026

Summary

Publish libcpex_ffi.a as signed GitHub Release artifacts on every
semver tag push, so downstream Go (and future language) bindings can
link against a pre-built static library without installing a Rust
toolchain. Adds a versioned FFI ABI contract so cross-version
mismatches surface as a loud init() panic instead of silent UB.

Why

Today every consumer of the CPEX Go binding has to clone this repo
and run cargo build --release themselves. That pushes the Rust
toolchain into every downstream CI (kagenti-extensions, anyone
embedding CPEX) and turns the FFI ABI into a runtime surprise
instead of a versioned contract. Publishing the .a as a signed
release artifact removes Rust from downstream builds and turns ABI
mismatches into a clean version-resolution error.

What ships

  • A new release-ffi.yaml workflow on semver-strict tag push.
  • A matrix-build over 5 target tuples → one signed tarball each.
  • A consumer-facing scripts/download-ffi-artifact.sh for the
    verify-and-unpack recipe.
  • An FFI_ABI_VERSION integer baked into the crate and checked at
    Go binding init.
  • An operator + integrator doc at crates/cpex-ffi/RELEASE.md.

Changes

Path Change
crates/cpex-ffi/src/lib.rs Add pub const FFI_ABI_VERSION: u32 = 1; and extern "C" fn cpex_ffi_abi_version(). Section docs define what counts as an ABI-breaking change.
go/cpex/abi.go (new) expectedFFIABIVersion = 1 + init() panic-on-mismatch. Self-contained cgo preamble (cgo declarations don't cross files in this package).
scripts/release/build-artifact.sh (new) Builds libcpex_ffi.a for a given TARGET Rust triple, stages tarball with VERSION / FFI_ABI / LICENSE, emits sha256 companion. Maps Rust triples to our tuple naming (linux-amd64-gnu, etc.).
scripts/release/sign-artifact.sh (new) Cosign keyless sign-blob over every tarball + the aggregate SHA256SUMS in DIST_DIR.
scripts/download-ffi-artifact.sh (new) Consumer-facing: auto-detects tuple via uname (with musl/gnu probe), downloads, verifies sha256, verifies cosign signature against the canonical workflow identity, unpacks. Idempotent on re-run.
.github/workflows/release-ffi.yaml (new) Trigger v[0-9]+.[0-9]+.[0-9]+ + v[0-9]+.[0-9]+.[0-9]+-* (semver-strict; excludes legacy / vendor tags). 5-tuple matrix build, separate sign-and-release job. Prereleases auto-flagged. Idempotent gh release create / upload --clobber. All github.ref_name interpolations flow through env: blocks.
crates/cpex-ffi/RELEASE.md (new) Operator + integrator doc: artifact schema, target matrix, helper-script and manual consume recipes, FFI ABI policy, troubleshooting.
CHANGELOG.md Unreleased entry under Added.

Target matrix

Tuple Rust target Runner
linux-amd64-gnu x86_64-unknown-linux-gnu ubuntu-latest
linux-arm64-gnu aarch64-unknown-linux-gnu ubuntu-22.04-arm
linux-amd64-musl x86_64-unknown-linux-musl ubuntu-latest
linux-arm64-musl aarch64-unknown-linux-musl ubuntu-22.04-arm
darwin-arm64 aarch64-apple-darwin macos-14

darwin-amd64 and Windows deferred; matrix is easy to extend later.

Artifact bundle

For each release tag vX.Y.Z and tuple:

cpex-ffi-vX.Y.Z-<tuple>.tar.gz          # libcpex_ffi.a + VERSION + FFI_ABI + LICENSE
cpex-ffi-vX.Y.Z-<tuple>.tar.gz.sha256
cpex-ffi-vX.Y.Z-<tuple>.tar.gz.sig      # cosign keyless
cpex-ffi-vX.Y.Z-<tuple>.tar.gz.crt

Plus one aggregate SHA256SUMS + .sig + .crt for the whole release.

Validation

A dry-run tag v0.0.0-ffi.test.1 was pushed against this branch
before this PR opened. Results (release + tag have since been
cleaned up):

  • Workflow triggered correctly under the semver-strict prerelease
    branch of the tag matcher.
  • All 5 matrix builds succeeded (longest ~1m31s).
  • Sign-and-release job completed in 18s; produced 23 release
    assets (5 tarballs × 4 files + 3-file aggregate SHA256SUMS bundle)
    flagged as prerelease.
  • Tarball contents verified: all four staged files present;
    VERSION.git_sha matched the tagged commit; FFI_ABI=1;
    _cpex_ffi_abi_version symbol exported in the published .a.
  • scripts/download-ffi-artifact.sh end-to-end against the
    published release: SHA256 verified, tarball unpacked, idempotency
    short-circuit fires on re-run.
  • Cosign certificate + signature + blob chain verified (cert
    identity regex matches the canonical workflow path; OIDC issuer
    matches GitHub Actions).

Local consequences for the existing in-tree builds: cargo check -p cpex-ffi, make rust-build-release, and go test ./go/cpex/...
all pass — the Go binding's new init() ABI check fires at every
test-binary load and is non-fatal because expectedFFIABIVersion
matches the just-built library's FFI_ABI_VERSION.

Notes for reviewers

  • FFI ABI bump policy. See the new section in lib.rs and the
    same in RELEASE.md. Adding a new RC_* code at the end is not
    a break; renaming / removing / re-signing any extern is. Bump
    flows through to go/cpex/abi.go's expectedFFIABIVersion in
    the same PR.
  • Why one PR. The artifact schema, ABI versioning contract, and
    the workflow that produces them are one contract; splitting them
    leaves an awkward middle state where scripts exist but no
    published artifact exercises them. The dry-run validation gate
    (above) is the right test, not a second PR.
  • Why semver-strict tag patterns. Loose v* would match
    vendor-bump tags, accidental v1 / v-test, etc. The two
    patterns v[0-9]+.[0-9]+.[0-9]+ + v[0-9]+.[0-9]+.[0-9]+-*
    only fire on vMAJOR.MINOR.PATCH (optionally prerelease) and
    cleanly exclude this repo's pre-v legacy tags (0.1.0,
    plugins.dev1).
  • Security note on workflow. Every ${{ github.ref_name }}
    interpolation that reaches a run: block flows through an env:
    variable and is referenced as "$VAR" (quoted). No direct
    expression substitution into shell.
  • Node 20 deprecation warnings. GitHub shows generic warnings
    about actions/checkout@v4, actions/upload-artifact@v4, and
    actions/download-artifact@v4 still running on Node 20 — GitHub
    will auto-force Node 24 on these actions starting June 16, 2026.
    No code change needed.
  • Cosign verification edge case. Networks that block
    rekor.sigstore.dev (some corporate egress filters, Cisco
    Umbrella, etc.) will fail the default cosign verify because it
    queries the transparency log online. The signature itself is
    fine; the user would need --insecure-ignore-tlog to verify
    without Rekor. A follow-up could have the signing job emit a
    cosign bundle (--bundle file.bundle) that embeds the
    Rekor inclusion proof inline, removing the online dependency.
    Out of scope here; worth a separate small PR if the egress
    pattern shows up in practice.

Out of scope (deferred to future PRs)

  • Generated C headers via cbindgen. The Go binding embeds C
    declarations inline in ffi.go; no other language binding needs
    headers today.
  • OCI artifact mirror to ghcr.io/contextforge-org/cpex-ffi. The
    GitHub Releases attachments are the source of truth; mirror is
    easy to add later.
  • darwin-amd64 and Windows targets.
  • Reproducibility beyond what cargo --locked gives us.

@araujof araujof requested review from jonpspri and terylt as code owners June 3, 2026 10:21
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
@araujof araujof force-pushed the feat/ffi_prebuilt_a branch from 373c0e9 to 87399f3 Compare June 3, 2026 10:57
@araujof araujof added enhancement New feature or request framework Go labels Jun 3, 2026
@araujof araujof added this to CPEX Jun 3, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in CPEX Jun 3, 2026
@araujof araujof linked an issue Jun 3, 2026 that may be closed by this pull request
@araujof araujof moved this from Backlog to In review in CPEX Jun 3, 2026
@araujof araujof linked an issue Jun 3, 2026 that may be closed by this pull request
araujof added 3 commits June 3, 2026 14:45
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
@terylt terylt merged commit 3f1566c into feat/apl_rust Jun 3, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in CPEX Jun 3, 2026
@terylt terylt deleted the feat/ffi_prebuilt_a branch June 3, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Go bindings (cpex-ffi + cgo)

2 participants