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
31 changes: 31 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Pull Request Labeler configuration
# Ref: https://github.com/actions/labeler

feat:
- changed-files:
- any-glob-to-any-file: 'proto/**/*'
- any-glob-to-any-file: 'src/**/*'
- any-glob-to-any-file: 'mojo/**/*'

fix:
- changed-files:
- any-glob-to-any-file: '.github/workflows/*'

docs:
- changed-files:
- any-glob-to-any-file: '*.md'
- any-glob-to-any-file: 'locales/*.md'

contracts:
- changed-files:
- any-glob-to-any-file: 'proto/**/*'

rust:
- changed-files:
- any-glob-to-any-file: 'src/**/*'
- any-glob-to-any-file: 'Cargo.toml'

mojo:
- changed-files:
- any-glob-to-any-file: 'mojo/**/*'
- any-glob-to-any-file: 'pixi.toml'
176 changes: 169 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@ jobs:
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup protoc
if: steps.guard.outputs.skip == 'false'
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Rust
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Check formatting
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
run: cargo fmt --all --check
- name: Clippy
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
run: cargo clippy --all-targets --all-features -- -D warnings

rust-build-and-test:
Expand All @@ -63,11 +68,16 @@ jobs:
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup protoc
if: steps.guard.outputs.skip == 'false'
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Rust
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
uses: actions/cache@v4
with:
path: |
Expand All @@ -76,9 +86,161 @@ jobs:
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
run: cargo build
- name: Test
if: steps.guard.outputs.skip != 'true'
if: steps.guard.outputs.skip == 'false'
run: cargo test


mojo-build:
name: Mojo Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Skip if no pixi.toml
id: guard
run: |
if [ ! -f pixi.toml ]; then
echo "::notice::No pixi.toml yet; skipping Mojo build/test."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup Pixi
if: steps.guard.outputs.skip == 'false'
uses: prefix-dev/setup-pixi@v0.8.1
with:
pixi-version: latest
cache: true
- name: Install dependencies (pixi install)
if: steps.guard.outputs.skip == 'false'
run: pixi install
- name: Populate generated/python (Mojo requirement)
if: steps.guard.outputs.skip == 'false'
run: |
# Mojo tests depend on Python stubs. Ensure they exist.
# Using the same pinned buf version for consistency.
curl -sSL https://github.com/bufbuild/buf/releases/download/v1.68.4/buf-Linux-x86_64 -o buf
chmod +x buf
./buf generate --template buf.gen.yaml
- name: Mojo check (pixi run check)
if: steps.guard.outputs.skip == 'false'
run: |
export PYTHONPATH=$PYTHONPATH:$(pwd)/generated/python
pixi run check
- name: Mojo test (pixi run test)
if: steps.guard.outputs.skip == 'false'
run: |
export PYTHONPATH=$PYTHONPATH:$(pwd)/generated/python
pixi run test


buf-lint:
name: buf Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Skip if no buf.yaml
id: guard
run: |
if [ ! -f buf.yaml ] && [ ! -f buf.yml ]; then
echo "::notice::No buf.yaml yet; skipping buf lint."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup buf
if: steps.guard.outputs.skip == 'false'
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
version: "1.68.4"
- name: Lint
if: steps.guard.outputs.skip == 'false'
run: buf lint

buf-breaking:
name: buf Breaking-Change Gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Skip if no buf.yaml
id: guard
run: |
if [ ! -f buf.yaml ] && [ ! -f buf.yml ]; then
echo "::notice::No buf.yaml yet; skipping buf breaking."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup buf
if: steps.guard.outputs.skip == 'false'
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
version: "1.68.4"
- name: Resolve baseline ref
if: steps.guard.outputs.skip == 'false'
id: baseline
run: |
if [ -n "${{ github.base_ref }}" ]; then
git fetch origin "${{ github.base_ref }}" --depth=50
ref="origin/${{ github.base_ref }}"
if ! git ls-tree -r "$ref" | grep -E 'buf\.yaml|buf\.yml' >/dev/null 2>&1; then
echo "::notice::Baseline ref $ref has no buf configuration; skipping."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "ref=$ref" >> $GITHUB_OUTPUT
echo "skip=false" >> $GITHUB_OUTPUT
fi
else
if git rev-parse HEAD~1 >/dev/null 2>&1; then
ref="HEAD~1"
if ! git ls-tree -r "$ref" | grep -E 'buf\.yaml|buf\.yml' >/dev/null 2>&1; then
echo "::notice::Baseline ref $ref has no buf configuration; skipping."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "ref=$ref" >> $GITHUB_OUTPUT
echo "skip=false" >> $GITHUB_OUTPUT
fi
else
echo "::notice::No prior commit; skipping."
echo "skip=true" >> $GITHUB_OUTPUT
fi
fi
- name: Breaking-change check
if: steps.guard.outputs.skip == 'false' && steps.baseline.outputs.skip == 'false'
run: buf breaking --against ".git#ref=${{ steps.baseline.outputs.ref }}"

buf-generate:
name: buf Generate (dry-run verify)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Skip if no buf.gen.yaml
id: guard
run: |
if [ ! -f buf.gen.yaml ]; then
echo "::notice::No buf.gen.yaml yet; skipping buf generate."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup buf
if: steps.guard.outputs.skip == 'false'
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
version: "1.68.4"
- name: Generate and verify no drift
if: steps.guard.outputs.skip == 'false'
run: |
buf generate --template buf.gen.yaml
if [ -d generated ] && ! git diff --quiet --exit-code -- generated/; then
echo "::error::generated/ is out of sync with proto/. Run 'buf generate' locally and commit the result."
git diff --stat -- generated/
exit 1
fi
48 changes: 47 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
permissions:
contents: read
security-events: write
actions: read
steps:
- uses: actions/checkout@v4
- name: Skip if no Cargo.toml
Expand Down Expand Up @@ -76,7 +77,52 @@ jobs:
continue-on-error: true
- name: Upload SARIF
if: steps.guard.outputs.skip != 'true'
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
continue-on-error: true # Allow CI to pass even if GitHub Advanced Security is not enabled


mojo-security:
name: Mojo Security Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Mojo static analysis (skipped until mojoproject.toml exists)
env:
MODULAR_AUTH: ${{ secrets.MODULAR_AUTH_TOKEN }}
run: |
if [ ! -f mojoproject.toml ]; then
echo "::notice::No mojoproject.toml yet; skipping Mojo static analysis."
exit 0
fi
curl -fsSL https://get.modular.com -o install-modular.sh
sh install-modular.sh
export PATH="$HOME/.modular/bin:$PATH"
modular install mojo
mojo check .


buf-schema-baseline:
name: buf Schema Security Baseline
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Skip if no buf.yaml
id: guard
run: |
if [ ! -f buf.yaml ] && [ ! -f buf.yml ]; then
echo "::notice::No buf.yaml yet; skipping schema security baseline."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup buf
if: steps.guard.outputs.skip != 'true'
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Lint schema (security pass)
if: steps.guard.outputs.skip != 'true'
run: buf lint

33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Build artifacts
target/
dist/
build/
*.bin
golden_directive.bin

# Dependencies
node_modules/
.pixi/

# Rust
Cargo.lock

# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.venv/
.env
*.egg-info/
generated/**/__pycache__/

# Mojo
.mojo/

# IDEs
.vscode/
.idea/
*.swp
*.swo
.DS_Store
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ This file is the operational core for Claude. Gemini CLI and Claude MUST follow

## 🎯 Architectural Intent
- **Core Mission:** Become the canonical typed boundary for the vtuber program β€” every cross-service value crosses through here, internal and external developers consume the same generated SDKs.
- **Primary Stack:** Rust, Protobuf (proto3), buf, tonic, ts-proto, mypy-protobuf
- **System Nature:** vtuber-contracts is the build-time source of truth for all inter-service typed boundaries in the vtuber-* program. It defines proto3 schemas for messages such as ConversationDirective, VoiceProfile, and Persona, then runs codegen to publish a Rust crate, Python typed stubs (.pyi), and TypeScript declaration files consumed by every other vtuber-* repo and by the public SDK shipped through vtuber-api.
- **Primary Stack:** Rust, Mojo (via Pixi), Protobuf (proto3), buf, tonic, ts-proto
- **System Nature:** vtuber-contracts is the build-time source of truth for all inter-service typed boundaries in the vtuber-* program. It defines proto3 schemas for messages such as ConversationDirective, VoiceProfile, and Persona, then runs codegen to publish three consumer surfaces: a Rust crate (via tonic-build), a Mojo binding package (via Pixi + Python interop β€” see ADR-004), and TypeScript declarations (via ts-proto) consumed by every other vtuber-* repo and by the public SDK shipped through vtuber-api.

## 🧬 Automated Lifecycle Management
1. **Research Sync:** When `./scripts/update_notebookLM.sh` is executed:
Expand Down
23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "vtuber-contracts"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
description = "Typed interface contracts (proto3) for the vtuber-* program β€” generates a Rust crate, Python typed stubs, and TypeScript declarations consumed by every other vtuber-* repo."
license = "MIT"
repository = "https://github.com/echo-layer/vtuber-contracts"
readme = "README.md"
keywords = ["vtuber", "grpc", "protobuf", "contracts"]
categories = ["api-bindings"]

[dependencies]
prost = "0.13"
prost-types = "0.13"
tonic = { version = "0.12", default-features = false, features = ["codegen", "prost"] }

[build-dependencies]
tonic-build = { version = "0.12", default-features = false, features = ["prost"] }

[dev-dependencies]
serde_yaml = "0.9"
serde = { version = "1", features = ["derive"] }
Loading
Loading