Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
25f32ad
docs: add Chinese architecture writing rules
MapleEve Jun 11, 2026
0f2ca33
refactor: break pipeline contract registry cycle
MapleEve Jun 12, 2026
8179a7d
refactor: keep HTTP errors at API boundary
MapleEve Jun 12, 2026
ff7f92f
refactor: cut stage provider registry loop
MapleEve Jun 12, 2026
4fb352a
refactor: add provider capability preflight
MapleEve Jun 12, 2026
0f5cd49
refactor: add architecture cycle gate
MapleEve Jun 12, 2026
db8f005
ci: require exact-ref release evidence
MapleEve Jun 12, 2026
541bed7
style: align app formatting
MapleEve Jun 12, 2026
c1cadec
test: add docs code drift gate
MapleEve Jun 12, 2026
4e3f20a
refactor: add transcription admission budgets
MapleEve Jun 12, 2026
eeccf77
refactor: bound memory-sensitive providers
MapleEve Jun 12, 2026
939545a
refactor: move transcription submission into application
MapleEve Jun 12, 2026
99181f2
refactor: move transcription records into application
MapleEve Jun 12, 2026
cbcfc86
refactor: add transcription admission resource snapshot
MapleEve Jun 13, 2026
f59733d
refactor: gate runtime dependency edges
MapleEve Jun 13, 2026
9e67037
refactor: move job status contract to infra
MapleEve Jun 13, 2026
b355687
refactor: add infra job adapters
MapleEve Jun 13, 2026
00856ac
refactor: gate pipeline metadata contract
MapleEve Jun 13, 2026
95627e6
fix: keep alignment cache-only fully offline
MapleEve Jun 13, 2026
6725b41
test: harden release validation gates
MapleEve Jun 13, 2026
a7c697e
ci: update pip audit baseline
MapleEve Jun 13, 2026
b984677
feat: require rust kernel by default
MapleEve Jun 13, 2026
d369062
test: fail closed when rust wheel is missing
MapleEve Jun 13, 2026
022b9df
ci: allow claude review more turns
MapleEve Jun 13, 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
19 changes: 12 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ MAX_UPLOAD_BYTES=2147483648

# Runtime cache and conversion limits.
JOBS_MAX_CACHE=200
TRANSCRIPTION_MAX_ACTIVE_JOBS=200
TRANSCRIPTION_MAX_IN_FLIGHT_JOBS=4
TRANSCRIPTION_MIN_FREE_DISK_BYTES=1073741824
FFMPEG_TIMEOUT_SEC=1800

# Optional idle model unload. Defaults to 180 seconds (3 minutes). Set to 0
Expand All @@ -42,14 +45,13 @@ FFMPEG_TIMEOUT_SEC=1800
# with the most free memory.
MODEL_IDLE_TIMEOUT_SEC=180

# Runtime mode for optional Rust-backed provider/kernel paths.
# off — default; use Python implementations.
# required — selected Rust-backed paths must run and hard-fail on import/call errors.
# Runtime mode for Rust-backed provider/kernel paths.
# required — default; selected Rust-backed paths must run and hard-fail on import/call errors.
# off — explicit rollback only; use Python implementations.
# Currently selected paths: voiceprint scoring, result post-processing, and
# artifact manifest helper contracts.
# CI/Docker packaging still validates the Rust extension directly even when
# the runtime default is off.
RUST_KERNEL_MODE=off
# CI/Docker packaging validates the Rust extension directly.
RUST_KERNEL_MODE=required

# UID/GID the container process runs as. Must match the owner of DATA_DIR
# and MODEL_CACHE_DIR on the host, otherwise writes fail. On a typical
Expand Down Expand Up @@ -111,13 +113,16 @@ WHISPERX_ALIGN_CACHE_ONLY=0
# Noise reduction defaults. Omitting denoise_model in the API uses
# DENOISE_MODEL; explicitly sending denoise_model=none disables denoising for
# that request. DENOISE_SNR_THRESHOLD only gates DeepFilterNet skips;
# noisereduce runs whenever selected.
# noisereduce still respects DENOISE_MAX_AUDIO_DURATION_SEC.
DENOISE_MODEL=none
DENOISE_SNR_THRESHOLD=10.0
DENOISE_MAX_AUDIO_DURATION_SEC=7200

# Speaker matching and diarization/embedding defaults.
VOICEPRINT_THRESHOLD=0.75
EMBEDDING_DIM=256
PYANNOTE_MIN_DURATION_OFF=0.5
MIN_EMBED_DURATION=1.5
MAX_EMBED_DURATION=10.0
EMBEDDING_PRELOAD_MAX_AUDIO_DURATION_SEC=1800
WHISPERX_ALIGN_MAX_AUDIO_DURATION_SEC=7200
23 changes: 22 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ jobs:
- name: Run public release scan
run: python voscript-api/scripts/public_release_scan.py --root .

architecture-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run architecture gate
run: python voscript-api/scripts/architecture_gate.py --root . --check

docs-code-drift-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run docs/code drift gate
run: python voscript-api/scripts/docs_code_drift_gate.py --root . --check

lint:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -78,4 +98,5 @@ jobs:
run: |
pip-audit -r app/requirements.txt \
--ignore-vuln PYSEC-2022-42969 \
--ignore-vuln CVE-2026-1839
--ignore-vuln CVE-2026-1839 \
--ignore-vuln CVE-2025-3000
2 changes: 1 addition & 1 deletion .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ jobs:

claude_args: |
--model ${{ env.CLAUDE_MODEL }}
--max-turns 8
--max-turns 16
220 changes: 209 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,117 @@ on:
- 'v*'
workflow_dispatch:

permissions:
contents: read

env:
PYTHON_VERSION: "3.11"
RUST_KERNEL_MODE: required

jobs:
publish:
resolve-source:
name: resolve-source
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
source-sha: ${{ steps.source.outputs.sha }}
source-ref: ${{ steps.source.outputs.ref }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve immutable source ref
id: source
run: |
set -euo pipefail
SOURCE_SHA="$(git rev-parse HEAD)"
echo "sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT"
echo "ref=${GITHUB_REF}" >> "$GITHUB_OUTPUT"
echo "Resolved release source ${GITHUB_REF} to ${SOURCE_SHA}"

public-release-scan:
name: public-release-scan
runs-on: ubuntu-latest
needs: resolve-source
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Run public release scan
run: python voscript-api/scripts/public_release_scan.py --root .

lint-format:
name: lint-format
runs-on: ubuntu-latest
needs: resolve-source
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
- name: Install ruff
run: python -m pip install ruff
- name: Run lint and format checks
run: |
ruff check app/ --ignore E501
ruff format --check app/

unit-security:
name: unit-security
runs-on: ubuntu-latest
needs: resolve-source
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
- name: Install test and security dependencies
run: |
python -m pip install pytest pytest-cov fastapi httpx numpy aiofiles starlette python-multipart pip-audit
- name: Run unit and security tests
env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
run: |
pytest tests/unit/ tests/test_security.py tests/test_voiceprint_db.py tests/test_job_service.py \
-p pytest_cov \
-v --tb=short --no-header
- name: Run pip-audit
run: |
pip-audit -r app/requirements.txt \
--ignore-vuln PYSEC-2022-42969 \
--ignore-vuln CVE-2026-1839 \
--ignore-vuln CVE-2025-3000

rust-wheel:
name: rust-wheel
runs-on: ubuntu-latest
needs:
- resolve-source
- public-release-scan
- lint-format
- unit-security
outputs:
wheel-name: ${{ steps.wheel.outputs.name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: ${{ env.PYTHON_VERSION }}
cache: pip

- name: Install Rust toolchain
Expand All @@ -31,24 +127,119 @@ jobs:
- name: Install maturin
run: python -m pip install "maturin>=1.13,<2"

- name: Check Rust formatting
run: cargo fmt --manifest-path crates/voscript_core/Cargo.toml -- --check

- name: Run Rust clippy
run: cargo clippy --manifest-path crates/voscript_core/Cargo.toml --features python-bindings --all-targets -- -D warnings

- name: Run Rust tests
run: cargo test --manifest-path crates/voscript_core/Cargo.toml

- name: Build Rust wheel
run: python -m maturin build --release --manifest-path crates/voscript_core/Cargo.toml --features extension-module --out dist

- name: Stage Rust wheel for Docker context
- name: Verify wheel artifact
id: wheel
run: |
set -euo pipefail
mkdir -p app/.wheelhouse
wheel_count="$(find dist -maxdepth 1 -name 'voscript_core-*.whl' | wc -l | tr -d ' ')"
if [ "$wheel_count" != "1" ]; then
echo "Expected exactly one voscript_core wheel, found $wheel_count" >&2
find dist -maxdepth 1 -type f >&2
exit 1
fi
wheel_path="$(find dist -maxdepth 1 -name 'voscript_core-*.whl' -print -quit)"
cp "$wheel_path" app/.wheelhouse/
echo "name=$(basename "$wheel_path")" >> "$GITHUB_OUTPUT"

- name: Upload exact-ref wheel artifact
uses: actions/upload-artifact@v4
with:
name: voscript-core-wheel-${{ needs.resolve-source.outputs.source-sha }}
path: dist/voscript_core-*.whl
if-no-files-found: error
retention-days: 1

docker-smoke:
name: docker-smoke
runs-on: ubuntu-latest
needs:
- resolve-source
- rust-wheel
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}

- name: Download exact-ref wheel artifact
uses: actions/download-artifact@v4
with:
name: voscript-core-wheel-${{ needs.resolve-source.outputs.source-sha }}
path: app/.wheelhouse

- name: Verify downloaded wheel
run: |
set -euo pipefail
test -f "app/.wheelhouse/${{ needs.rust-wheel.outputs.wheel-name }}"

- name: Build Docker image with exact-ref Rust wheel
run: |
docker build ./app \
--build-arg "VOSCRIPT_CORE_WHEEL=${{ needs.rust-wheel.outputs.wheel-name }}" \
-t voscript-release-smoke:${{ needs.resolve-source.outputs.source-sha }}

- name: Run container Rust extension smoke
run: |
docker run --rm \
-e RUST_KERNEL_MODE=required \
voscript-release-smoke:${{ needs.resolve-source.outputs.source-sha }} \
python -c "from providers.kernel_bridge import artifact_manifest_contract, core_smoke, postprocess_segments, status_payload_contract, voiceprint_score; result = core_smoke({'source': 'release'}); assert result['ok'] is True; decision = voiceprint_score({'query_embedding': [1.0, 0.0], 'candidates': [{'speaker_id': 'spk_release', 'name': 'Release', 'embedding': [1.0, 0.0], 'sample_count': 1, 'sample_spread': None}], 'threshold': 0.75, 'asnorm_threshold': 0.5, 'cohort': None}); assert decision['matched_id'] == 'spk_release'; processed = postprocess_segments({'aligned_segments': [{'start': 0.0, 'end': 1.0, 'text': 'hello', 'speaker': 'SPEAKER_00'}], 'speaker_map': {}}); assert processed['segments'][0]['speaker_label'] == 'SPEAKER_00'; manifest = artifact_manifest_contract({'manifest_version': 'artifact_manifest.v1', 'stable': [{'name': 'result', 'filename': 'result.json', 'role': 'primary_result', 'media_type': 'application/json', 'required_for_result': True}], 'optional': [], 'experimental': []}); assert manifest['stable'][0]['filename'] == 'result.json'; status = status_payload_contract({'status': 'queued', 'updated_at': '2026-06-09T00:00:00+00:00', 'filename': 'private/audio.wav'}); assert status['filename'] == 'audio.wav'"

- name: Run container healthz smoke
run: |
set -euo pipefail
cid="$(docker run -d -e DEVICE=cpu -e ALLOW_NO_AUTH=1 -e RUST_KERNEL_MODE=required voscript-release-smoke:${{ needs.resolve-source.outputs.source-sha }})"
trap 'docker logs "$cid" || true; docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
for _ in $(seq 1 60); do
if docker exec "$cid" python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8780/healthz', timeout=2).read()" >/dev/null 2>&1; then
exit 0
fi
sleep 2
done
echo "Container did not pass /healthz smoke in time" >&2
exit 1

publish:
name: publish
runs-on: ubuntu-latest
needs:
- resolve-source
- public-release-scan
- lint-format
- unit-security
- rust-wheel
- docker-smoke
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-source.outputs.source-sha }}

- name: Download exact-ref wheel artifact
uses: actions/download-artifact@v4
with:
name: voscript-core-wheel-${{ needs.resolve-source.outputs.source-sha }}
path: app/.wheelhouse

- name: Verify downloaded wheel
run: |
set -euo pipefail
test -f "app/.wheelhouse/${{ needs.rust-wheel.outputs.wheel-name }}"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand All @@ -67,10 +258,13 @@ jobs:

- name: Compute image tags
id: tags
env:
SOURCE_SHA: ${{ needs.resolve-source.outputs.source-sha }}
run: |
set -euo pipefail
GHCR_IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')"
DOCKERHUB_IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/voscript"
TAGS="$GHCR_IMAGE:latest,$DOCKERHUB_IMAGE:latest"
TAGS="$GHCR_IMAGE:latest,$DOCKERHUB_IMAGE:latest,$GHCR_IMAGE:sha-$SOURCE_SHA,$DOCKERHUB_IMAGE:sha-$SOURCE_SHA"
# Tag-triggered builds (release or push-tag) get the version tag too.
# Strip leading "v" for Docker Hub convention (0.7.0), keep raw ref for GHCR.
if [ "${{ github.event_name }}" = "release" ] || [ "${{ github.event_name }}" = "push" ]; then
Expand All @@ -80,14 +274,18 @@ jobs:
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

- name: Build and push
- name: Build and push exact-ref image
uses: docker/build-push-action@v6
with:
context: ./app
platforms: linux/amd64
push: true
build-args: |
VOSCRIPT_CORE_WHEEL=${{ steps.wheel.outputs.name }}
VOSCRIPT_CORE_WHEEL=${{ needs.rust-wheel.outputs.wheel-name }}
labels: |
org.opencontainers.image.revision=${{ needs.resolve-source.outputs.source-sha }}
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.ref.name=${{ needs.resolve-source.outputs.source-ref }}
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
Loading
Loading