Skip to content

ci(003): assert the manylinux_2_17 glibc floor at release time#6

Merged
bartekus merged 1 commit into
mainfrom
ci/d1-glibc-floor-guard
Jul 3, 2026
Merged

ci(003): assert the manylinux_2_17 glibc floor at release time#6
bartekus merged 1 commit into
mainfrom
ci/d1-glibc-floor-guard

Conversation

@bartekus

@bartekus bartekus commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Retire and guard TODO(D1): prove the Linux release binaries honor the
manylinux_2_17 glibc floor, and add a standing release-time assertion so they
cannot silently stop honoring it.

Why

release.yml builds the two Linux targets with cargo zigbuild --target <triple>.2.17 so the manylinux_2_17 wheel tag and the npm libc: ["glibc"]
claim are honest. That path was authored but never validated (the TODO(D1)
comment), so nothing confirmed the produced binaries actually run on an
old-glibc host, and nothing stopped a future toolchain bump from raising the
floor and turning the tag into a lie.

What I did

  • Validated both Linux triples locally: the zigbuild .2.17 binaries
    reference a maximum glibc symbol of exactly GLIBC_2.17, and they run on a
    real glibc-2.17 userland (manylinux2014 aarch64 native + x86_64 emulated,
    ldd (GNU libc) 2.17), executing the full verify-certificate path.
  • Guarded: release.yml now reads the ELF dynamic symbol table after each
    glibc-floor build and fails the release if any referenced GLIBC_x.y exceeds
    2.17. Proven to pass at 2.17 and trip below it.
  • Coupled, not drift: added the floor guarantee as a spec 003 behavior
    requirement, and regenerated .derived for 003.

Test

  • Local: symbol-floor scan (objdump + in-container readelf) and runtime smoke
    in manylinux2014 for both arches, all green; the guard passes at floor 2.17
    and trips at 2.14.
  • Governance: spec-spine compile / index check / lint --fail-on-warn all
    clean.
  • The guard runs only on tagged (v*) releases, so its first live exercise is
    the next release; the logic here is validated against the real built binaries.

The Linux release binaries are cross-linked against glibc 2.17 so the
manylinux_2_17 wheel tag and the npm libc:["glibc"] claim are honest, but
that floor was never verified (TODO(D1)). Validated it: the zigbuild `.2.17`
path produces binaries whose maximum referenced glibc symbol is exactly 2.17,
and they run on a real glibc-2.17 userland (manylinux2014 aarch64 + x86_64),
executing the full verify path.

Add a standing guard so a toolchain bump cannot silently raise the floor and
turn the tag into a lie: release.yml reads the ELF dynamic symbol table after
each glibc-floor build and fails the release if any referenced GLIBC_x.y
symbol exceeds 2.17. Document the guarantee as a spec 003 behavior requirement
so the change is coupled, not drift.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

AI Code Review

Bugs and Logic Errors

Dead code: empty-MAX guard is unreachable under pipefail

The script opens with set -euo pipefail, then builds MAX via a pipeline that ends with grep:

MAX="$(readelf --dyn-syms --wide "$BIN" \
  | grep -oE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' \
  | sed 's/GLIBC_//' | sort -uV | tail -1)"

If the binary carries no GLIBC_ symbols (i.e., the build configuration is broken in exactly the way the script is designed to detect), grep exits 1. With pipefail active, the whole pipeline exits 1, and set -e terminates the script before MAX is ever assigned. The if [ -z "$MAX" ] block with the informative error message is therefore dead code in the only case it was written to handle.

Fix: append || true to the pipeline (or use || :) so bash falls through to the explicit check:

MAX="$(readelf --dyn-syms --wide "$BIN" \
  | grep -oE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' \
  | sed 's/GLIBC_//' | sort -uV | tail -1)" || true

Minor: --dyn-syms reads symbol-name annotations, not the authoritative version-requirement section

readelf --dyn-syms extracts GLIBC_x.y by pattern-matching @@GLIBC_x.y suffixes on symbol names in .dynsym. The canonical source is .gnu.version_r, read via readelf -V (--version-info), which directly lists GLIBC_x.y version requirements without depending on name annotations surviving truncation or future binutils formatting changes. The current approach works on today's ubuntu runners, but readelf -V would be more robust against toolchain drift.

Security Vulnerabilities

None. The ${{ matrix.triple }} expansion in the shell script is sourced from the static workflow matrix (not user-controlled input), so there is no injection risk.

Spec-Spine Compliance

Coupling is complete: specs/003-distribution/spec.md was updated alongside release.yml, and both .derived/ shard hashes were regenerated. No coupling gate issue.

Performance

No concerns. The check is a single readelf pass on one binary per target, negligible relative to the build.


Automated review by Claude. Diff size: 46 lines.

@bartekus
bartekus merged commit 0748483 into main Jul 3, 2026
9 checks passed
@bartekus
bartekus deleted the ci/d1-glibc-floor-guard branch July 3, 2026 00:23
bartekus added a commit that referenced this pull request Jul 3, 2026
First release since v0.2.0, bundling PRs #5, #6, and #7. PR #7 is a breaking
change to verify-certificate, so this is a minor bump under 0.x.

- Breaking (#7, spec 001): verify-certificate requires a verified platform seal
  by default; an unsealed or unadjudicated certificate exits 1. Restore the prior
  behavior with --allow-unsealed. --require-sealed is now a deprecated no-op.
- Added (#6, spec 003): release builds assert the manylinux_2_17 glibc floor.
- Fixed (#5): hardened the verify cores, exit-code contract, and release pipeline.

Version bumped in lockstep across Cargo.toml, npm/package.json (main + five
platform pins), and py/pyproject.toml; Cargo.lock and the codebase-index shard
regenerated. New CHANGELOG.md carries the migration note for pinned tenants.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant