Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,146 @@ jobs:
- name: Test
run: npm test

flatbuffers_drift:
name: Flatbuffers bindings match schemas

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt

- uses: actions/setup-node@v4
with:
node-version: '20'

# Pinned deliberately. Different flatc releases format their output
# differently, so this job compares against one specific compiler.
#
# The version is read from build.rs rather than repeated here: two copies
# would let the compiler and the pin drift apart, which is the class of
# mistake this job exists to catch.
- name: Read pinned flatc version
id: flatc
run: |
# `|| true` so a failed grep does not abort the step under `bash -e`
# before the message below can explain what went wrong. It fails
# closed either way; this makes the failure legible.
version="$(grep -oP 'PINNED_FLATC: &str = "\K[^"]+' rust/build.rs || true)"
if [ -z "$version" ]; then
echo "could not read PINNED_FLATC from rust/build.rs" >&2
echo "(has the declaration been reformatted?)" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

# Downloaded outside the checkout on purpose: anything left in the working
# tree would show up as untracked, and the staleness check below treats
# untracked files as drift.
#
# The checksum is not ceremony. Release assets can be replaced by anyone
# with upstream write access, and a substituted binary would self-report
# the expected version, pass the gate below, and then dictate what this
# repository is required to commit.
#
# It is necessarily pinned by hand, so it is the one value that does NOT
# follow PINNED_FLATC automatically: moving the pin without updating this
# fails with "computed checksum did NOT match", which reads as a
# supply-chain alarm rather than "you forgot the hash". Update both
# together. The asset filename is likewise not stable across flatc
# releases and may need revisiting on a bump.
- name: Install pinned flatc
env:
FLATC_VERSION: ${{ steps.flatc.outputs.version }}
FLATC_SHA256: 9e3b46402076388e61ceeb07344e95a71b2c67fa54b861eaaef905bbe9311b86
run: |
curl -sSfL -o "$RUNNER_TEMP/flatc.zip" \
"https://github.com/google/flatbuffers/releases/download/v${FLATC_VERSION}/Linux.flatc.binary.g++-13.zip"
echo "${FLATC_SHA256} $RUNNER_TEMP/flatc.zip" | sha256sum -c -
unzip -q "$RUNNER_TEMP/flatc.zip" -d "$HOME/.flatc"
chmod +x "$HOME/.flatc/flatc"
echo "$HOME/.flatc" >> "$GITHUB_PATH"

- name: Verify flatc version
env:
FLATC_VERSION: ${{ steps.flatc.outputs.version }}
run: |
# A silently-different compiler would make this whole job compare the
# wrong thing and pass, which is the failure mode it exists to prevent.
installed="$(flatc --version | awk '{print $NF}')"
if [ "$installed" != "$FLATC_VERSION" ]; then
echo "expected flatc $FLATC_VERSION, got '$installed'" >&2
exit 1
fi

# Deleting first makes a regeneration that quietly does nothing fail
# closed. Deletions of tracked files ARE visible to the staleness check
# below, whereas a no-op regeneration leaves a clean tree that reads as
# success. Without this the job's correctness rests on the build script
# actually executing, which today holds only because this is the one Rust
# job without a cargo cache -- an implicit property nothing enforces.
#
# The TypeScript half also catches ORPHANS, which nothing else can see.
# When a table is deleted, renamed, or moves namespace, flatc stops
# emitting its old .ts file but does not remove it, so the stale file is
# never regenerated, never modified, and reads as a clean tree. Deleting
# every file carrying flatc's own header first means an orphan stays
# deleted and shows up as drift. The marker is what makes this safe to do
# by pattern: the hand-written sources in typescript/src do not carry it.
- name: Remove generated bindings so a no-op regeneration is visible
run: |
rm -f rust/src/generated/*_generated.rs
grep -rl "automatically generated by the FlatBuffers compiler" typescript/src \
| xargs -r rm -f

# build.rs runs cargo fmt itself and panics if anything goes wrong, so a
# regeneration that cannot run fails here rather than reaching the
# comparison below having quietly changed nothing.
- name: Regenerate Rust bindings
working-directory: rust
run: FREENET_REGEN_FLATBUFFERS=1 cargo build

# No `npm install`: flatc-schemas only shells out to the flatc binary, so
# installing the package tree would add a minute and a network flake
# surface for nothing.
- name: Regenerate TypeScript bindings
working-directory: typescript
run: npm run flatc-schemas

# Until this job existed nothing compared the schemas to the bindings: the
# build regenerated only when a contributor happened to have flatc, and CI
# never installed it, so a schema edit that was never regenerated compiled
# and passed against bindings describing the previous schema.
# `git status --porcelain`, not `git diff --exit-code`: a new .fbs file
# generates entirely NEW binding files, which are untracked, and a plain
# diff reports a clean tree for them. That would let a schema merge with no
# committed bindings at all -- the loudest version of the drift this job
# exists to catch, sailing straight past it.
# The version in the remediation comes from the same step output as the
# install, not a literal: a hardcoded one silently rots when the pin moves,
# and the advice then tells contributors to install a compiler the build
# script explicitly rejects.
- name: Fail if the committed bindings are stale
env:
FLATC_VERSION: ${{ steps.flatc.outputs.version }}
run: |
changes="$(git status --porcelain)"
if [ -n "$changes" ]; then
echo "The committed flatbuffers bindings do not match the schemas." >&2
echo >&2
echo "$changes" >&2
echo >&2
echo "Regenerate them and commit the result:" >&2
echo " (cd rust && FREENET_REGEN_FLATBUFFERS=1 cargo build)" >&2
echo " (cd typescript && npm run flatc-schemas)" >&2
echo "This needs flatc $FLATC_VERSION specifically; another release reformats every file." >&2
exit 1
fi

claude-ci-analysis:
name: Claude CI Analysis

Expand Down
Loading
Loading