fix(ffi): regenerate the drifted cbindgen header and gate it in CI - #277
Open
YuanYuYuan wants to merge 2 commits into
Open
fix(ffi): regenerate the drifted cbindgen header and gate it in CI#277YuanYuYuan wants to merge 2 commits into
YuanYuYuan wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Regenerates the committed FFI header and adds CI drift detection.
Changes:
- Updates the C header with the missing context namespace field and constants.
- Adds cbindgen and a header freshness script.
- Adds a dedicated CI freshness job.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
scripts/test-pure-rust.nu |
Adds the regeneration check. |
flake.nix |
Adds cbindgen to build inputs. |
crates/hiroz-go/hiroz/hiroz_ffi.h |
Regenerates the C FFI surface. |
.github/workflows/ci.yml |
Runs the freshness check in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The committed header lacked the namespace_ field that CContextConfig has carried since it was added. cgo sizes its struct from this file, so Go allocated 80 bytes where the Rust side reads 88 and dereferenced cfg.namespace past the end of the allocation on every advanced-config ContextBuilder.Build(). Regenerated with the cbindgen the flake pins (0.29.3). Also restores 11 parameter-type constants, KEEP_ALL_CACHE_DEPTH, and corrected doc text. Refs #270
Nothing regenerated the header in CI, so its drift from the Rust structs was invisible. cbindgen was absent from every dev shell, and build.rs degrades its absence to a non-fatal cargo:warning=, so enabling the ffi feature alone would not have caught this. Adds rust-cbindgen to commonBuildInputs and a check-ffi-header check that deletes the header, regenerates it, and fails on any diff -- mirroring check-python-stubs, which gates the generated Python stubs the same way. The deletion is what makes a build that generates nothing fail instead of reporting on the committed copy; cbindgen's absence is checked explicitly so that path cannot pass vacuously either. Runs as its own nix-based job because go-tests has no nix environment and cbindgen must come from the pinned dev shell -- 0.29.4 emits a constant 0.29.3 does not, so an unpinned version would itself read as drift. Closes item 3 of #270
YuanYuYuan
force-pushed
the
ci/ffi-header-fresh
branch
from
August 14, 2026 18:23
e20f37a to
2e0b16b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cbindgen generates
crates/hiroz-go/hiroz/hiroz_ffi.hfromcrates/hiroz/src/ffi/. hiroz commits the header, because cgo needs it at build time. No CI job regenerated it, so it drifted. The drift was not confined to documentation.This PR regenerates the header. It also adds a CI gate that fails on the next drift.
Refs #270 — the two header-drift follow-ups in that issue's second comment.
The defect
The committed header was missing the
namespace_field thatCContextConfighas carried since that field landed. cbindgen appends the trailing underscore, becausenamespaceis a C++ keyword.var cfg C.hiroz_context_config_tincontext.goCContextConfiginffi/context.rsnamespacesits at offset 80cfg.namespacecstr_to_strdereferences it as a C stringAny Go caller that sets an advanced-config option reaches this path. That option is what selects
hiroz_context_create_with_configincontext.go.The 80/88 figures come from a
sizeofprobe compiled against each version of the header. The x86-64 layout gives the same answer:enable_loggingoccupies offset 72, tail padding runs to 80, the appended pointer occupies 80–87.What this PR does
crates/hiroz-go/hiroz/hiroz_ffi.h(52 insertions, 4 deletions)rust-cbindgentocommonBuildInputsflake.nixcheck-ffi-headercommand, wired into the pipelinescripts/test-pure-rust.nuffi-header-freshjob, mirroringpython-stubs-fresh.github/workflows/ci.ymlBeyond
namespace_, the regenerated header adds theKEEP_ALL_CACHE_DEPTHconstant,DEPTH_RECURSIVE, ten parameter-type constants (NOT_SETthroughSTRING_ARRAY), and corrected doc text forDEFAULT_HISTORY_DEPTHandhiroz_service_client_wait_for_service. None of these had ever reached the header.rust-cbindgenwas in no dev shell. That is the root cause: CI could not regenerate the header, even if a job had asked it to.Four details of the check are load-bearing rather than incidental:
Dentry.build.rsdegrades a missing cbindgen to a non-fatalcargo:warning=. Without the assertion the build succeeds, writes nothing, and the check reports on whatever the tree already held.touchescrates/hiroz/build.rsgo-testshas no Nix environment, and cbindgen must come from the pinned dev shell. cbindgen 0.29.4 emits aCDR_HEADER_LEconstant that 0.29.3 does not, so an unpinned cbindgen reads as drift. The check prints the version it used.Evidence
The header on
mainis stale. The new check fails against it:Three inputs exercise the check. It detects the defect; it does not merely never fire.
mainas-is)e20f37a9610510ba8f870d853264dfd48bc8e382)PATHrmBreaking changes
sizeof(hiroz_context_config_t)Not affected: the Rust API, and the offsets of every pre-existing member. cbindgen appends the field at the end, so source compiled against the new header stays source-compatible.
Warning
Appending does not preserve ABI. A caller still linked against the 80-byte definition passes an 80-byte allocation to a library that reads
namespace_at offset 80 — the out-of-bounds read described above. The doc comment inherited from the Rust source says "to preserve ABI compatibility"; that is too strong. The accurate statement is offset compatibility plus a mandatory rebuild. This PR leaves the comment as-is.cgo rebuilds automatically for the in-tree Go bindings. Any out-of-tree C consumer holding a copy of the old header must regenerate it.
Coverage this does not have
None of these block the fix. They bound what green CI proves.
--features ffiunder-D warnings) is untouched, as are item 1 (the 22missing_safety_doccontracts), item 2 (thecast_slice_from_raw_partscast inserialize.rs) and item 5 (theRawPublisher::publish_bytesregression test). Item 4 is #273's.check-python-stubs.cfg.namespace_stays null from in-tree Go callers. This PR fixes the allocation size, not missing functionality.git checkout -- crates/hiroz-go/hiroz/hiroz_ffi.hrestores it. A comment in the check states this; nothing handles it automatically.Notes
#273 also edits
.github/workflows/ci.yml. It changesgo-testsandscripts/test-go.nu; this PR adds a new job and leavesgo-testsalone, so neither depends on the other.flake.nixhas twoextraShellHook = ''''occurrences (one, the other) thatnixfmt --checkflags. Both are pre-existing onmainand outside this diff. Left alone.