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
19 changes: 19 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,23 @@
# stay as plain git blobs: Go modules resolve through the module proxy (no LFS
# smudge), and the Kotlin/Python dylibs are small build inputs for the
# Maven/PyPI artifacts.
#
# The C carrier's dynamic libraries (staged by tooling/connect/cpp-build.mjs,
# Windows refresh via the `windows-smoke` lane of
# .github/workflows/cpp-connect.yml) are ~6 MB each and are tracked the same
# way. Its import library (`*.dll.lib`, ~150 KB) and `provenance.json` stay
# plain git blobs: they are small, and every checkout — including the lanes
# that only hash or diff the record — has to read them without an LFS fetch.
# C/C++ consumers clone the repo, so they need git-lfs (`git lfs install`
# once, `git lfs pull` in an existing clone).
crates/spoke-connect/bindings/swift/xcframework/**/*.a filter=lfs diff=lfs merge=lfs -text
crates/spoke-connect/bindings/cpp/native/**/*.dll filter=lfs diff=lfs merge=lfs -text
crates/spoke-connect/bindings/cpp/native/**/*.dylib filter=lfs diff=lfs merge=lfs -text

# The C contract header's bytes are part of the published C carrier record:
# `tooling/connect/cpp-build.mjs` hashes it into `headerSha256` in
# bindings/cpp/native/provenance.json, and consumers verify their copy against
# that hash. `eol=lf` keeps the checked-out bytes identical on every platform —
# without it a Windows `core.autocrlf=true` checkout hands the build CRLF bytes
# and records a hash no LF checkout can ever match.
crates/spoke-connect/bindings/cpp/include/spoke_connect.h text eol=lf
49 changes: 8 additions & 41 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
name: CI

on:
# Unfiltered on pull requests: verify-codegen / typescript / rust /
# verify-version / connect-identity / golden-vector-sync are required checks,
# and a required context a path filter skips can never be satisfied. The
# docs-only optimization survives on the push side below.
pull_request:
# Skip the compile/test matrix for docs-only changes; docs-site validation lives in docs.yml.
paths-ignore:
- 'docs/**'
- 'tooling/docs/**'
- '**/*.md'
push:
branches:
- main
Expand Down Expand Up @@ -125,50 +124,18 @@ jobs:
- name: Checkout
# actions/checkout@v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
# Full history so the path-filter diff below can compare base vs head.
fetch-depth: 0

- name: Setup Node.js
# actions/setup-node@v4.4.0 — Node 24 matches the release.yml npm publish surface
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 24

- name: Check identity-related paths changed
id: paths
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
else
base="${{ github.event.before }}"
head="${{ github.event.after }}"
fi
# First push to a branch has an all-zero `before` sha — diff against
# the empty tree so the gate still runs.
if [[ "$base" =~ ^0+$ ]]; then
base="$(git hash-object -t tree /dev/null)"
fi
# Fail open on diff errors: if git diff cannot resolve the base/head
# shas (e.g. force-push GC'd the old commit), run the proof anyway —
# a regression gate that silently skips on its own filter error is
# a vacuous pass. The warning makes the fallback visible.
if ! changed="$(git diff --name-only "$base" "$head" 2>/dev/null)"; then
echo "::warning::git diff failed for $base..$head; running identity proof anyway"
echo "run=true" >> "$GITHUB_OUTPUT"
else
matches="$(printf '%s\n' "$changed" | grep -Ec '^(tooling/connect-identity-proof/|packages/spoke-connect-ts/|\.github/workflows/ci\.yml)' || true)"
if [[ "${matches:-0}" -gt 0 ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
fi
fi

- name: Run connect identity proof
if: steps.paths.outputs.run == 'true'
# A required PR check runs on every pull request: a changed-path gate
# would report this context green without proving anything, and an
# unscheduled-proof pass is indistinguishable from a real one. The
# proof is zero-dependency, so the minute it costs is the honest price.
run: node tooling/connect-identity-proof/proof.mjs

rust:
Expand Down
54 changes: 39 additions & 15 deletions .github/workflows/cpp-connect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@ name: C++ connect (Windows + macOS)
# refresh artifact; macOS validates the committed osx-arm64 carrier, then both
# lanes prove exported symbols and run the shared C++ smoke.
on:
# Unfiltered on pull requests: windows-smoke / macos-smoke are required
# checks, so every PR schedules both carrier lanes. The push filter below is
# unchanged, so pushes outside those paths still skip them.
pull_request:
paths:
- Cargo.toml
- Cargo.lock
- crates/spoke-connect-capi/**
- crates/spoke-connect/src/**
- crates/spoke-connect/Cargo.toml
- crates/spoke-connect/bindings/cpp/include/**
- crates/spoke-connect/bindings/cpp/Smoke/**
- crates/spoke-connect/bindings/cpp/native/**
- tooling/connect/cpp-build.mjs
- tooling/connect/cpp-symbol-check.mjs
- tooling/connect/cpp-smoke.mjs
- .github/workflows/cpp-connect.yml
push:
branches:
- main
Expand Down Expand Up @@ -56,8 +46,11 @@ jobs:
CARGO_ENCODED_RUSTFLAGS: ""
steps:
- name: Checkout
# actions/checkout@v4.2.2
# actions/checkout@v4.2.2 — lfs: true so the symbol check, the smoke and
# the provenance check hash the real carrier, not an LFS pointer.
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
lfs: true

- name: Setup MSVC developer environment
# x64 selects the release MSVC toolchain used by the C++ smoke.
Expand Down Expand Up @@ -101,6 +94,17 @@ jobs:
throw "cl.exe version probe failed with exit code $LASTEXITCODE"
}

- name: Verify committed provenance
# Reports drift without cancelling the lane: the build, the smoke and
# the uploaded refresh artifact below still run, so a stale record can
# be replaced by a real rebuild instead of being hand-edited. The final
# `Fail on provenance drift` step re-fails the job from this step's
# *outcome* (continue-on-error leaves the outcome `failure`), so the
# lane can never end green while the committed bytes disagree.
id: provenance
continue-on-error: true
run: node tooling/connect/cpp-build.mjs --verify --target x86_64-pc-windows-msvc

- name: Build Windows carrier
run: node tooling/connect/cpp-build.mjs --target x86_64-pc-windows-msvc --toolchain 1.96.0

Expand Down Expand Up @@ -131,6 +135,18 @@ jobs:
if-no-files-found: error
retention-days: 5

- name: Fail on provenance drift
# The lane's verdict. `continue-on-error` above keeps the refresh
# artifact flowing, but the job still has to go red when the committed
# record does not describe the committed bytes — it reads the raw step
# outcome, so drift is never absorbed into a green run.
if: always()
shell: pwsh
run: |
if ("${{ steps.provenance.outcome }}" -ne "success") {
throw "committed provenance does not describe the committed win-x64 bytes; apply the uploaded refresh artifact (a genuine Windows rebuild) before this lane can pass"
}

macos-smoke:
name: macos-smoke
runs-on: macos-14
Expand All @@ -142,8 +158,11 @@ jobs:
CARGO_ENCODED_RUSTFLAGS: ""
steps:
- name: Checkout
# actions/checkout@v4.2.2
# actions/checkout@v4.2.2 — lfs: true so the committed carrier arrives as
# bytes; the provenance check and the symbol check read it.
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
lfs: true

- name: Setup Node.js
# actions/setup-node@v4.4.0 — Node 24
Expand Down Expand Up @@ -179,6 +198,11 @@ jobs:
clang++ --version
rustc +1.96.0 -Vv

- name: Verify committed provenance
# Fails hard: this lane produces no artifact, so nothing downstream has
# to run for the drift to be actionable.
run: node tooling/connect/cpp-build.mjs --verify --target aarch64-apple-darwin

- name: Show native provenance
shell: bash
run: cat crates/spoke-connect/bindings/cpp/native/provenance.json
Expand Down
18 changes: 6 additions & 12 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: Docs site

# Documentation gate: validates every documentation change — the site under
# docs/ (with its tooling) and any Markdown file elsewhere. `ci.yml` skips its
# compile/test matrix for documentation-only change sets, so this workflow is
# the validation entry point; it fails if `pnpm docs:build` breaks, if an EN
# page ships without its CN twin (twin-parity), or if an internal link resolves
# to a missing page (dead-link).
# docs/ (with its tooling) and any Markdown file elsewhere. It fails if
# `pnpm docs:build` breaks, if an EN page ships without its CN twin
# (twin-parity), or if an internal link resolves to a missing page
# (dead-link). `Build docs site` is a required check, so the pull-request
# trigger is unfiltered — a path filter would leave that context unscheduled
# and unmergeable on non-docs PRs; the push side keeps the docs-only filter.
# Deploy: on push to main, the built site is published to GitHub Pages via the
# official Pages actions (upload-pages-artifact + deploy-pages).
#
Expand All @@ -22,13 +23,6 @@ name: Docs site

on:
pull_request:
paths:
- docs/**
- tooling/docs/**
- '**/*.md'
- .github/workflows/docs.yml
- package.json
- pnpm-lock.yaml
push:
branches:
- main
Expand Down
15 changes: 3 additions & 12 deletions .github/workflows/xcframework.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,16 @@ name: Xcframework

# Dedicated xcframework job: assembles the three-slice spoke_connectFFI
# xcframework from the checkout's Rust sources and fails the job when the
# built bytes drift from the committed (LFS) artifact. Path-filtered so the
# macOS runner is used only when the FFI surface (or this automation itself)
# changes — never per-push unconditional. Mirrors ci.yml's event set.
# built bytes drift from the committed (LFS) artifact. `xcframework` is a
# required check, so every pull request runs it and pays the macOS runner; the
# push side keeps the FFI-surface filter, and ci.yml mirrors that event set.
#
# Deliberately off-filter: crates/spoke-operations/**, crates/spoke-schemas/**,
# crates/spoke-fixture-toy-world/** — they change artifact bytes but not the
# FFI ABI; ABI-relevant drift there is already forced by ci.yml's
# verify-binding-regen.sh gate (generated Swift tree diff).
on:
pull_request:
paths:
- crates/spoke-connect/src/**
- crates/spoke-connect/uniffi-bindgen.rs
- crates/spoke-connect/Cargo.toml
- Cargo.lock
- tooling/connect/build-swift-xcframework.sh
- tooling/connect/verify-xcframework-drift.sh
- crates/spoke-connect/bindings/swift/generated/**
- .github/workflows/xcframework.yml
push:
branches:
- main
Expand Down
Loading
Loading