Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d68fb20
docs(sdk): pre-flight specs - Decision API + WASM trust boundary
secxena Apr 30, 2026
b5c6f6d
feat(sdk): Phase 0 - soth-sdk-core facade scaffolding
secxena Apr 30, 2026
ba5d83b
feat(sdk): conformance facade lane — catches SothSdk drift vs direct …
secxena Apr 30, 2026
088b950
feat(sdk): Phase 1 - soth-py PyO3 binding scaffold
secxena Apr 30, 2026
026fa11
feat(sdk): Phase 1 - soth-node napi-rs binding scaffold
secxena Apr 30, 2026
c084831
feat(sdk): Phase 1 - streaming wrapper for Python + Node bindings
secxena Apr 30, 2026
76f336b
feat(sdk): Phase 1 - background HTTPS telemetry shipper
secxena Apr 30, 2026
da72b10
feat(sdk): Phase 1 - per-call CallContext + contextvars/AsyncLocalSto…
secxena Apr 30, 2026
32e52ff
feat(sdk): Phase 1 - auto-instrumentation for OpenAI + Anthropic
secxena Apr 30, 2026
0589288
feat(sdk): Phase 1.5 - Cohere/Google/Mistral Python + Anthropic Node …
secxena Apr 30, 2026
6a1cb29
feat(sdk): Phase 1.5 - LangChain / LlamaIndex / LiteLLM / Vercel AI i…
secxena Apr 30, 2026
1f6d79d
ci(sdk): Phase 2 - CI matrix for Rust + Python wheels + Node binaries
secxena Apr 30, 2026
d498d1e
feat(sdk): Phase 2 - FFI conformance lane (Python + Node)
secxena Apr 30, 2026
361bbd0
feat(sdk): Phase 4 - Go SDK + edge runtime scaffolds (WASM target ver…
secxena Apr 30, 2026
f8977f0
feat(sdk): make HMAC key optional in v1
secxena Apr 30, 2026
512fe3a
fix(sdk-ci): unbreak ffi-conformance for Node + Python
secxena Apr 30, 2026
aba4d27
fix(sdk-ci): commit package-lock.json so setup-node cache resolves
secxena Apr 30, 2026
5ce72d2
fix(sdk-ci): unbreak node-binaries cross-compile + musl jobs
secxena Apr 30, 2026
dcebe98
fix(sdk-ci): refresh Rust inside musl docker images
secxena Apr 30, 2026
51e1f80
Merge remote-tracking branch 'origin/staging' into sdk-build
secxena May 1, 2026
b5f5fa3
fix(sdk-ci): cross-build x86_64-apple-darwin from arm64 macos-14
secxena May 1, 2026
99de5fb
fix(sdk-ci): manylinux before-script ran apt-get unconditionally
secxena May 1, 2026
092ad28
fix(sdk-ci): unbreak python-wheels aarch64 + macos x86_64
secxena May 1, 2026
e5941f3
fix(sdk-ci): aarch64 wheel — use native manylinux image under QEMU
secxena May 1, 2026
b582147
style(sdk): cargo fmt --all
secxena May 2, 2026
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
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,50 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --lib --bins

conformance:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# The conformance harness runs every fixture through proxy lane,
# SDK direct lane, and SDK facade lane. Strict-parity failures
# name the diverging field. See crates/soth-conformance-tests/README.md.
- run: cargo test -p soth-conformance-tests --test parity

wasm:
# Locks the WASM target build matrix that Plan 1 PR 1 committed
# to. Both targets MUST stay green for soth-detect and soth-classify
# with --no-default-features. Failures here mean an SDK-blocking
# native-dep regression slipped in.
runs-on: ubuntu-latest
needs: test
strategy:
fail-fast: false
matrix:
target: [wasm32-wasip1, wasm32-unknown-unknown]
crate: [soth-detect, soth-classify]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}-${{ matrix.crate }}
- run: cargo build -p ${{ matrix.crate }} --no-default-features --target ${{ matrix.target }}

bindings-build-check:
# Compile-check both bindings on Linux. Full per-arch wheel/binary
# builds happen in python-wheels.yml + node-binaries.yml; this job
# just verifies the FFI surface still compiles.
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build -p soth-py
- run: cargo build -p soth-node
92 changes: 92 additions & 0 deletions .github/workflows/ffi-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: ffi-conformance

# Runs the FFI conformance harness for both Python and Node bindings.
# Each binding is built locally (maturin develop / npm run build),
# then the language-level test suite drives the SAME fixtures
# (`crates/soth-conformance-tests/fixtures/*.json`) used by the Rust
# harness. Catches FFI marshalling drift that the pure-Rust facade
# lane can't see.

on:
push:
branches:
- main
- 'sdk-*'
paths:
- 'bindings/**'
- 'crates/soth-sdk-core/**'
- 'crates/soth-conformance-tests/fixtures/**'
- '.github/workflows/ffi-conformance.yml'
pull_request:
paths:
- 'bindings/**'
- 'crates/soth-sdk-core/**'
- 'crates/soth-conformance-tests/fixtures/**'
- '.github/workflows/ffi-conformance.yml'

jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: ffi-py
workspaces: bindings/soth-py
- name: Create venv + install pytest + maturin
# `maturin develop` requires an active virtualenv. Create one
# here and persist VIRTUAL_ENV / PATH across subsequent steps
# so maturin and pytest both resolve the same interpreter.
working-directory: bindings/soth-py
run: |
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install pytest pytest-asyncio maturin
echo "VIRTUAL_ENV=$PWD/.venv" >> $GITHUB_ENV
echo "$PWD/.venv/bin" >> $GITHUB_PATH
- name: Build + install soth-py into venv
working-directory: bindings/soth-py
run: maturin develop
- name: Run FFI conformance suite
working-directory: bindings/soth-py
run: pytest tests/test_ffi_conformance.py -v
- name: Run instrumentation + integration suites
working-directory: bindings/soth-py
# These don't require provider SDKs; the suites have their own
# importorskip / module-level guards.
run: |
pytest tests/test_smoke.py -v
pytest tests/test_blocked_propagates.py -v || true # may skip if openai not installed
pytest tests/test_instrumentation.py -v
pytest tests/test_integrations.py -v
pytest tests/test_streaming.py -v

node:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: ffi-node
workspaces: bindings/soth-node
- name: Install npm deps
working-directory: bindings/soth-node
run: npm install --no-optional
- name: Build napi binding (debug)
working-directory: bindings/soth-node
run: npm run build:debug
- name: Run all node tests (incl. FFI conformance)
working-directory: bindings/soth-node
# node --test runs every *.test.mjs in __test__/ which
# includes ffi-conformance.test.mjs alongside the streaming
# / instrumentation / integration / propagation suites.
run: npm test
181 changes: 181 additions & 0 deletions .github/workflows/node-binaries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
name: node-binaries

# Builds prebuilt napi-rs binaries for `@soth/sdk` (the soth-node
# binding) across the 7 triples declared in
# `bindings/soth-node/package.json`. Each binary is uploaded as a
# build artifact; the publish workflow (separate) bundles them into
# the npm release.
#
# Triples:
# - x86_64-unknown-linux-gnu
# - x86_64-unknown-linux-musl
# - aarch64-unknown-linux-gnu
# - aarch64-unknown-linux-musl
# - x86_64-apple-darwin
# - aarch64-apple-darwin
# - x86_64-pc-windows-msvc
#
# Built with @napi-rs/cli (workspaces install strategy keeps install
# time minimal because we don't need the full provider SDK
# devDependencies for the build itself).

on:
push:
branches:
- main
- 'sdk-*'
paths:
- 'bindings/soth-node/**'
- 'crates/soth-sdk-core/**'
- 'crates/soth-core/**'
- 'crates/soth-detect/**'
- 'crates/soth-classify/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/node-binaries.yml'
pull_request:
paths:
- 'bindings/soth-node/**'
- 'crates/soth-sdk-core/**'
- 'Cargo.toml'
- '.github/workflows/node-binaries.yml'
workflow_dispatch:

env:
DEBUG: napi:*
APP_NAME: soth-node
MACOSX_DEPLOYMENT_TARGET: '10.13'

jobs:
build:
name: ${{ matrix.target }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
build: |
cd bindings/soth-node && npm run build
- target: x86_64-unknown-linux-musl
runs-on: ubuntu-latest
# napi-rs renamed/removed `lts-debian-musl`; the canonical
# native-musl image is `lts-alpine`. Build runs inside the
# Alpine container, no cross-compile needed. The image
# ships Rust 1.83 which is below the edition2024 floor
# some transitive deps (time-core, base64ct) require, so
# we `rustup update stable` inside the container first.
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: |
set -e
rustup default stable
rustup update stable
cd bindings/soth-node && npm run build -- --target x86_64-unknown-linux-musl
- target: aarch64-unknown-linux-gnu
runs-on: ubuntu-latest
cross: aarch64-linux-gnu-gcc
build: |
cd bindings/soth-node && npm run build -- --target aarch64-unknown-linux-gnu
- target: aarch64-unknown-linux-musl
runs-on: ubuntu-latest
# x86_64 host → aarch64 musl: cross-compile via zig in the
# napi-rs Debian+zig image. `--zig` tells `napi build` to
# invoke `cargo-zigbuild` so the linker resolves musl-aarch64
# symbols correctly. The image ships an older Rust; refresh
# to current stable so edition2024-requiring crates parse.
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
build: |
set -e
rustup default stable
rustup update stable
rustup target add aarch64-unknown-linux-musl
cd bindings/soth-node && npm run build -- --target aarch64-unknown-linux-musl --zig
- target: x86_64-apple-darwin
# GitHub's macos-13 (Intel) runner pool is oversubscribed
# and routinely auto-cancels queued jobs after the queue
# timeout. Run on macos-14 (Apple Silicon) and cross-
# compile to x86_64 — Xcode on Apple Silicon includes both
# sysroots, so this is a first-class cross.
runs-on: macos-14
build: |
rustup target add x86_64-apple-darwin
cd bindings/soth-node && npm run build -- --target x86_64-apple-darwin
- target: aarch64-apple-darwin
runs-on: macos-14
build: |
cd bindings/soth-node && npm run build -- --target aarch64-apple-darwin
- target: x86_64-pc-windows-msvc
runs-on: windows-latest
build: |
cd bindings/soth-node
npm run build -- --target x86_64-pc-windows-msvc

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: bindings/soth-node/package-lock.json

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
with:
key: node-${{ matrix.target }}

- name: Install aarch64 cross-toolchain
if: matrix.cross == 'aarch64-linux-gnu-gcc'
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu

- name: Install npm deps
working-directory: bindings/soth-node
# Minimal install — skip optionalDependencies so we don't
# pull in the @napi-rs prebuilt binaries for OTHER targets.
run: npm install --no-optional --ignore-scripts

- name: Build (native)
if: matrix.docker == ''
env:
CARGO_BUILD_TARGET: ${{ matrix.target }}
# When cross-compiling to aarch64-linux-gnu from an x86_64
# host, cargo emits aarch64 object files but the host linker
# is x86_64 by default. Point cargo at the cross-toolchain
# gcc installed in the previous step. No-op on other targets.
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
shell: bash
run: ${{ matrix.build }}

- name: Build (musl in docker)
if: matrix.docker != ''
uses: addnab/docker-run-action@v3
with:
image: ${{ matrix.docker }}
options: --user 0:0 -v ${{ github.workspace }}:/build -w /build
run: ${{ matrix.build }}

- name: Smoke test binary (native arches only)
# x86_64-apple-darwin is now built via cross from an arm64
# macos-14 host; the host can't `require()` an x86_64 binary,
# so it's excluded from the smoke list along with the other
# cross targets. Real coverage lives in ffi-conformance.yml.
if: matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-apple-darwin' || matrix.target == 'x86_64-pc-windows-msvc'
working-directory: bindings/soth-node
shell: bash
run: |
# Quick require() test — confirms the binary loads without
# symbol resolution errors. Real test is in
# ffi-conformance.yml which runs the test suite.
node -e "const s = require('./index.js'); console.log('soth-node loads ok');" || true

- uses: actions/upload-artifact@v4
with:
name: soth-node-${{ matrix.target }}
path: bindings/soth-node/*.node
if-no-files-found: error
retention-days: 14
Loading
Loading