Compare commits

..

1 commit

Author SHA1 Message Date
dependabot[bot]
f8c5c14c94 chore(deps): bump github.com/golang-jwt/jwt/v5 in /veza-backend-api
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 19:01:15 +00:00
3642 changed files with 118316 additions and 252943 deletions

View file

@ -1,27 +0,0 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"build",
"ci",
"chore",
"revert",
"security"
]
],
"subject-case": [0],
"header-max-length": [2, "always", 120],
"body-max-line-length": [1, "always", 200],
"footer-max-line-length": [0]
}
}

View file

@ -1,10 +1,10 @@
# Règles de Développement UI - Projet SaaS
## 0. Scope v0.901 (priorité absolue)
## 0. Scope v0.601 (priorité absolue)
- **Référence** : `docs/V0_901_RELEASE_SCOPE.md` et `docs/SCOPE_CONTROL.md`
- Avant toute modification : vérifier si le changement est **dans le scope v0.901**
- **v0.803 livré** : Voir `docs/archive/V0_803_RELEASE_SCOPE.md`
- **Référence** : `docs/V0_601_RELEASE_SCOPE.md` et `docs/SCOPE_CONTROL.md`
- Avant toute modification : vérifier si le changement est **dans le scope v0.601**
- **Autorisé v0.601** : lots à définir (voir V0_601_RELEASE_SCOPE.md)
- **Interdit** : nouvelles routes/pages hors scope, nouvelles dépendances (sauf correctif sécurité)
- En cas de doute : ne pas ajouter. Créer une issue pour une version ultérieure.

View file

@ -1,79 +0,0 @@
# cleanup-failed.yml — workflow_dispatch only.
#
# Tears down the kept-alive failed-deploy color (the inactive one
# that survived a Phase D / Phase F failure for forensics).
# Operator triggers this once they have read the journalctl output.
#
# Hard safety in playbooks/cleanup_failed.yml: refuses to destroy
# the currently-active color.
name: Veza cleanup failed-deploy color
on:
workflow_dispatch:
inputs:
env:
description: "Environment to clean up"
required: true
type: choice
options: [staging, prod]
color:
description: "Color to destroy (must NOT be the active one)"
required: true
type: choice
options: [blue, green]
concurrency:
group: cleanup-${{ inputs.env }}
cancel-in-progress: false
jobs:
cleanup:
name: Destroy ${{ inputs.color }} app containers in ${{ inputs.env }}
runs-on: [self-hosted, incus]
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install ansible
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible
ansible-galaxy collection install community.general
- name: Write vault password
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run cleanup_failed.yml
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ inputs.env }}.yml \
playbooks/cleanup_failed.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ inputs.env }} \
-e target_color=${{ inputs.color }}
- name: Upload Ansible log
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}
path: ${{ runner.temp }}/ansible-cleanup-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

View file

@ -1,360 +0,0 @@
# Veza deploy pipeline.
#
# Triggers (intentionally narrow — see SECURITY note below):
# workflow_dispatch → operator-supplied env + sha
# (push:main + tag:v* are commented OUT until provisioning is
# complete — see docs/RUNBOOK_DEPLOY_BOOTSTRAP.md. Re-enable
# once secrets/runner/vault are in place and a manual run via
# workflow_dispatch has been verified GREEN.)
#
# SECURITY: this workflow runs on a self-hosted runner with access to
# the Incus unix socket (effectively root on the host). DO NOT add
# `pull_request` or any fork-influenced trigger here — an attacker-
# controlled fork would be able to `incus exec` arbitrarily. The
# narrow trigger list above is the security boundary.
#
# Sequence : build (3 jobs in parallel) → upload artifacts → deploy.
name: Veza deploy
on:
# push: # GATED — uncomment after first
# branches: [main] # successful workflow_dispatch run
# tags: ['v*'] # see RUNBOOK_DEPLOY_BOOTSTRAP.md
workflow_dispatch:
inputs:
env:
description: "Environment to deploy"
required: true
default: staging
type: choice
options: [staging, prod]
release_sha:
description: "Full git SHA to deploy (defaults to current HEAD if empty)"
required: false
type: string
concurrency:
# Only one deploy per env at a time. Newer pushes cancel older
# in-flight builds for the same env (the user almost always wants
# the newer commit).
group: deploy-${{ github.ref_type == 'tag' && 'prod' || 'staging' }}
cancel-in-progress: true
env:
# Where build artefacts land. Set in Forgejo repo Variables :
# FORGEJO_REGISTRY_URL = https://forgejo.veza.fr/api/packages/talas/generic
REGISTRY_URL: ${{ vars.FORGEJO_REGISTRY_URL }}
jobs:
# =================================================================
# Resolve env + sha from the trigger.
# =================================================================
resolve:
name: Resolve env + SHA
runs-on: [self-hosted, incus]
outputs:
env: ${{ steps.r.outputs.env }}
sha: ${{ steps.r.outputs.sha }}
steps:
- name: Resolve
id: r
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
ENV="${{ inputs.env }}"
SHA="${{ inputs.release_sha || github.sha }}"
elif [ "${{ github.ref_type }}" = "tag" ]; then
ENV="prod"
SHA="${{ github.sha }}"
else
ENV="staging"
SHA="${{ github.sha }}"
fi
if ! echo "$SHA" | grep -Eq '^[0-9a-f]{40}$'; then
echo "SHA '$SHA' is not a 40-char git SHA"
exit 1
fi
echo "env=$ENV" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "Resolved env=$ENV sha=$SHA"
# =================================================================
# Build backend (Go).
# =================================================================
build-backend:
name: Build backend
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
cache-dependency-path: veza-backend-api/go.sum
- name: Test
working-directory: veza-backend-api
env:
VEZA_SKIP_INTEGRATION: "1"
run: go test ./... -short -count=1 -timeout 300s
- name: Build veza-api (CGO=0, static)
working-directory: veza-backend-api
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
run: |
go build -trimpath -ldflags "-s -w" \
-o ./bin/veza-api ./cmd/api/main.go
go build -trimpath -ldflags "-s -w" \
-o ./bin/migrate_tool ./cmd/migrate_tool/main.go
- name: Stage tarball contents
working-directory: veza-backend-api
run: |
STAGE="$RUNNER_TEMP/veza-backend"
mkdir -p "$STAGE/migrations"
cp ./bin/veza-api ./bin/migrate_tool "$STAGE/"
cp -r ./migrations/* "$STAGE/migrations/" || true
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-backend" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-backend/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Build stream (Rust).
# =================================================================
build-stream:
name: Build stream
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Set up Rust toolchain
run: |
command -v rustup >/dev/null || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source "$HOME/.cargo/env"
rustup target add x86_64-unknown-linux-musl
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
sudo apt-get update -qq && sudo apt-get install -y musl-tools
- name: Cache cargo + target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
veza-stream-server/target
key: deploy-${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
restore-keys: |
deploy-${{ runner.os }}-cargo-
- name: Test
working-directory: veza-stream-server
run: cargo test --workspace
- name: Build stream_server (musl static)
working-directory: veza-stream-server
run: |
cargo build --release --locked \
--target x86_64-unknown-linux-musl
- name: Stage tarball contents
working-directory: veza-stream-server
run: |
STAGE="$RUNNER_TEMP/veza-stream"
mkdir -p "$STAGE"
cp ./target/x86_64-unknown-linux-musl/release/stream_server "$STAGE/"
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-stream" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-stream/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Build web (React/Vite).
# =================================================================
build-web:
name: Build web
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Build design tokens
run: npm run build:tokens --workspace=@veza/design-system
- name: Build SPA
working-directory: apps/web
env:
VITE_API_URL: /api/v1
VITE_DOMAIN: ${{ needs.resolve.outputs.env == 'prod' && 'veza.fr' || 'staging.veza.fr' }}
VITE_RELEASE_SHA: ${{ needs.resolve.outputs.sha }}
run: npm run build
- name: Stage tarball contents
run: |
STAGE="$RUNNER_TEMP/veza-web"
mkdir -p "$STAGE"
cp -r apps/web/dist/* "$STAGE/"
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-web-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-web" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-web-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-web/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Deploy via Ansible. Runs on the self-hosted runner that has
# Incus socket access (label `incus`). Requires Forgejo secrets:
# ANSIBLE_VAULT_PASSWORD — unlocks group_vars/all/vault.yml
# FORGEJO_REGISTRY_TOKEN — same token the build jobs use,
# passed to ansible-playbook so
# the data containers can fetch
# the tarballs they were just sent.
# =================================================================
deploy:
name: Deploy via Ansible
needs: [resolve, build-backend, build-stream, build-web]
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Install ansible + community.general + community.postgresql + community.rabbitmq
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible python3-psycopg2 python3-pip
ansible-galaxy collection install \
community.general \
community.postgresql \
community.rabbitmq
- name: Write vault password to a tmpfile
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run deploy_data.yml (idempotent provisioning + ZFS snapshot)
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-data-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ needs.resolve.outputs.env }}.yml \
playbooks/deploy_data.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ needs.resolve.outputs.env }} \
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Run deploy_app.yml (blue/green)
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-app-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ needs.resolve.outputs.env }}.yml \
playbooks/deploy_app.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ needs.resolve.outputs.env }} \
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Upload Ansible logs (for forensics)
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-logs-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}
path: ${{ runner.temp }}/ansible-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

View file

@ -1,118 +0,0 @@
# rollback.yml — workflow_dispatch only.
#
# Two modes :
# fast — flip HAProxy back to the previous color. ~5s. Requires
# the target color's containers to still be alive
# (i.e., no later deploy has recycled them).
# full — re-run deploy_app.yml with a specific (older) release_sha.
# ~5-10min. The artefact must still be in the Forgejo
# registry (default retention 30 SHA per component).
#
# See docs/RUNBOOK_ROLLBACK.md for decision criteria.
name: Veza rollback
on:
workflow_dispatch:
inputs:
env:
description: "Environment to rollback"
required: true
type: choice
options: [staging, prod]
mode:
description: "Rollback mode"
required: true
type: choice
options: [fast, full]
target_color:
description: "(mode=fast only) color to flip back TO (the prior active one)"
required: false
type: choice
options: [blue, green]
release_sha:
description: "(mode=full only) 40-char SHA of the release to redeploy"
required: false
type: string
concurrency:
group: rollback-${{ inputs.env }}
cancel-in-progress: false
jobs:
rollback:
name: Rollback ${{ inputs.env }} (${{ inputs.mode }})
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- name: Validate inputs
run: |
if [ "${{ inputs.mode }}" = "fast" ] && [ -z "${{ inputs.target_color }}" ]; then
echo "mode=fast requires target_color"
exit 1
fi
if [ "${{ inputs.mode }}" = "full" ]; then
if [ -z "${{ inputs.release_sha }}" ]; then
echo "mode=full requires release_sha"
exit 1
fi
if ! echo "${{ inputs.release_sha }}" | grep -Eq '^[0-9a-f]{40}$'; then
echo "release_sha is not a 40-char git SHA"
exit 1
fi
fi
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ inputs.mode == 'full' && inputs.release_sha || github.ref }}
- name: Install ansible + collections
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible python3-psycopg2
ansible-galaxy collection install \
community.general \
community.postgresql \
community.rabbitmq
- name: Write vault password
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run rollback.yml
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
EXTRA="-e veza_env=${{ inputs.env }} -e mode=${{ inputs.mode }}"
if [ "${{ inputs.mode }}" = "fast" ]; then
EXTRA="$EXTRA -e target_color=${{ inputs.target_color }}"
else
EXTRA="$EXTRA -e veza_release_sha=${{ inputs.release_sha }}"
EXTRA="$EXTRA -e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}"
fi
ansible-playbook \
-i inventory/${{ inputs.env }}.yml \
playbooks/rollback.yml \
--vault-password-file "$VAULT_PASS_FILE" \
$EXTRA
- name: Upload Ansible log
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}
path: ${{ runner.temp }}/ansible-rollback-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

108
.github/workflows/backend-ci.yml vendored Normal file
View file

@ -0,0 +1,108 @@
name: Backend API CI
on:
push:
paths:
- "veza-backend-api/**"
- ".github/workflows/backend-ci.yml"
pull_request:
paths:
- "veza-backend-api/**"
- ".github/workflows/backend-ci.yml"
jobs:
test-unit:
runs-on: ubuntu-latest
defaults:
run:
working-directory: veza-backend-api
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
cache: true
- name: Download deps
run: go mod download
- name: Go vet and format check
run: |
go vet ./...
test -z "$(gofmt -l .)"
working-directory: veza-backend-api
- name: Run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Run unit tests
run: go test ./internal/handlers/... ./internal/services/... -short -coverprofile=coverage.out -covermode=atomic -timeout 5m
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: go-coverage
path: veza-backend-api/coverage.out
test-integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: veza_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veza_test?sslmode=disable
REDIS_URL: redis://localhost:6379
JWT_SECRET: test-jwt-secret-for-ci
APP_ENV: test
defaults:
run:
working-directory: veza-backend-api
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
cache: true
- name: Download deps
run: go mod download
- name: Run migrations
run: go run cmd/migrate_tool/main.go
continue-on-error: true
- name: Run integration tests
run: go test -tags=integration ./internal/... -timeout 15m

165
.github/workflows/cd.yml vendored Normal file
View file

@ -0,0 +1,165 @@
name: Veza CD
on:
push:
branches: [ "main" ]
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
jobs:
build:
name: Build and push images
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Push to registry: set repo secrets DOCKER_REGISTRY, DOCKER_REGISTRY_USERNAME, DOCKER_REGISTRY_PASSWORD
# Example: DOCKER_REGISTRY=ghcr.io/org/repo or registry.example.com/veza
- name: Build Backend Docker Image
run: |
docker build -t veza-backend-api:${{ github.sha }} -f veza-backend-api/Dockerfile.production veza-backend-api/
- name: Build Frontend Docker Image
run: |
docker build -t veza-frontend:${{ github.sha }} -f apps/web/Dockerfile.production apps/web/
- name: Build Stream Server Docker Image
run: |
docker build -t veza-stream-server:${{ github.sha }} -f veza-stream-server/Dockerfile.production veza-stream-server/
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: 'veza-backend-api:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Trivy scan frontend
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: 'veza-frontend:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Trivy scan stream server
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: 'veza-stream-server:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Generate SBOM
run: |
mkdir -p sbom
for svc in veza-backend-api veza-frontend veza-stream-server; do
trivy image --format cyclonedx --output "sbom/${svc}-${{ github.sha }}.json" "${svc}:${{ github.sha }}"
done
- name: Upload SBOM artifacts
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom/
- name: Push Images to Registry
if: vars.DOCKER_REGISTRY != ''
run: |
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
for svc in veza-backend-api veza-frontend veza-stream-server; do
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
done
- name: Install cosign
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.2.0'
- name: Sign images with cosign
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
run: |
for svc in veza-backend-api veza-frontend veza-stream-server; do
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
done
- name: Build Summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- Backend: veza-backend-api:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- Frontend: veza-frontend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- Stream Server: veza-stream-server:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
deploy:
name: Deploy to ${{ github.event.inputs.environment || 'staging' }}
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
environment: ${{ github.event.inputs.environment || 'staging' }}
steps:
- name: Deploy to Kubernetes
if: vars.KUBE_CONFIG_SET == 'true'
run: |
KUBECONFIG="${{ runner.temp }}/kubeconfig"
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
chmod 600 "$KUBECONFIG"
export KUBECONFIG
for svc in veza-backend-api veza-stream-server; do
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}" \
-n veza --record || echo "Skipping ${svc} (deployment not found)"
done
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
rm -f "$KUBECONFIG"
- name: Deployment Summary
run: |
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "- Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY
smoke-post-deploy:
name: Smoke tests post-deploy
runs-on: ubuntu-latest
needs: deploy
if: vars.STAGING_URL != ''
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install chromium --with-deps
- name: Run smoke tests
env:
PLAYWRIGHT_BASE_URL: ${{ vars.STAGING_URL }}
run: |
cd apps/web
npx playwright test --config=playwright.config.smoke.ts

View file

@ -1,249 +1,289 @@
name: Veza CI
name: Veza CI/CD
on:
push:
branches: ["main", "remediation/*", "feature/mvp-complete"]
pull_request:
branches: ["main", "feature/mvp-complete"]
workflow_dispatch:
env:
GIT_SSL_NO_VERIFY: "true"
NODE_TLS_REJECT_UNAUTHORIZED: "0"
push:
branches: [ "main", "remediation/*", "feature/mvp-complete" ]
pull_request:
branches: [ "main", "feature/mvp-complete" ]
workflow_dispatch: # Allow manual trigger
jobs:
# ===========================================================================
# Backend (Go) — build, test, lint, security
# ===========================================================================
backend:
name: Backend (Go)
runs-on: [self-hosted, incus]
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
backend-go:
name: Backend (Go)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
with:
go-version: "1.25"
cache: true
# go.mod/go.sum live under veza-backend-api, not repo root.
# Without this, setup-go warns "Dependencies file is not
# found" and skips the mod cache → adds ~60-90s per run.
cache-dependency-path: veza-backend-api/go.sum
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Cache Go tool binaries
id: go-tools-cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: ~/go/bin
key: ${{ runner.os }}-go-tools-govulncheck-golangci-lint-v2
# Save the cache even when later steps (Lint, Test, etc.)
# fail so the next run benefits from the installed tools.
save-always: true
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: true
- name: Install Go tools
# NOTE: golangci-lint v2 lives under the /v2/ module path.
# The old /cmd/ path still resolves to v1.64.x, which rejects
# v2-format .golangci.yml with "please use golangci-lint v2".
# Pinned versions so the cache key stays stable.
if: steps.go-tools-cache.outputs.cache-hit != 'true'
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- name: Install dependencies
run: npm ci
- name: Add ~/go/bin to PATH
run: echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Run govulncheck
run: |
cd veza-backend-api
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Build
run: go build ./...
working-directory: veza-backend-api
- name: Vet
run: |
cd veza-backend-api
go vet ./...
- name: Test
# -short + VEZA_SKIP_INTEGRATION=1 so testcontainers-go (which
# needs a Docker socket) is not invoked on the Forgejo runner.
# Integration tests run in a dedicated nightly job with DinD.
run: go test ./... -short -count=1 -timeout 300s -coverprofile=coverage.out
env:
VEZA_SKIP_INTEGRATION: "1"
working-directory: veza-backend-api
- name: Lint
run: npx turbo run lint --filter=veza-backend-api
- name: Lint
run: golangci-lint run ./... --timeout 5m
working-directory: veza-backend-api
- name: Test
run: npx turbo run test --filter=veza-backend-api
- name: Vet
run: go vet ./...
working-directory: veza-backend-api
- name: Build
run: npx turbo run build --filter=veza-backend-api
- name: Vulnerability check
run: govulncheck ./...
working-directory: veza-backend-api
rust-services:
name: Rust Services (Stream)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Coverage summary
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}')
echo "## Backend Coverage: $COVERAGE" >> $GITHUB_STEP_SUMMARY
working-directory: veza-backend-api
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# ===========================================================================
# Frontend (Web) — lint, typecheck, build, unit tests
# ===========================================================================
frontend:
name: Frontend (Web)
runs-on: [self-hosted, incus]
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Use Node.js
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Install dependencies
run: npm ci
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Sprint 2 design-system migrated to Style Dictionary; the
# generated tokens live in packages/design-system/dist/ which
# is gitignored. apps/web imports `@veza/design-system/tokens-generated`,
# so dist/ MUST exist before tsc/vitest/build runs.
# `prepare` in the package would normally cover npm ci, but
# this explicit step makes the dependency loud and runnable
# standalone for local debugging.
- name: Build design tokens
run: npm run build:tokens --workspace=@veza/design-system
- name: Install cargo-audit
run: cargo install cargo-audit
# Prevents drift between veza-backend-api/openapi.yaml and
# apps/web/src/types/generated/. Regenerates then fails if
# git diff is non-empty.
- name: Check OpenAPI types in sync
run: bash scripts/check-types-sync.sh
working-directory: apps/web
- name: Auditing Stream Server
run: |
cd veza-stream-server
cargo audit
- name: Lint
# ESLint warning baseline (v1.0.10 dette tech).
# Lowered from 1204 → 1108 after no-unused-vars sprint
# (134 → 0). Top contributors at this baseline :
# 757 no-restricted-syntax (custom design-system rule —
# Tailwind defaults / hex literals / native <button>)
# 139 @typescript-eslint/no-non-null-assertion
# 115 @typescript-eslint/no-explicit-any
# 47 react-hooks/exhaustive-deps
# 25 react-refresh/only-export-components
# 23 storybook/no-redundant-story-name
# CI fails on ANY new warning. Lower this number as
# warnings are resorbed ; never raise it.
run: npx eslint --max-warnings=1108 .
working-directory: apps/web
- name: Lint
run: npx turbo run lint --filter=veza-stream-server
- name: Typecheck
run: npx tsc --noEmit
working-directory: apps/web
- name: Build
run: npx turbo run build --filter=veza-stream-server
- name: Build
run: npm run build
working-directory: apps/web
- name: Test
run: npx turbo run test --filter=veza-stream-server
- name: Bundle size gate
run: node scripts/check-bundle-size.mjs
working-directory: apps/web
frontend:
name: Frontend (Web)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Audit dependencies
run: npm audit --audit-level=critical
- name: Install Dependencies
run: npm ci
- name: Unit tests
run: npx vitest run --reporter=verbose
working-directory: apps/web
- name: Security audit (npm)
run: npm audit --audit-level=critical
# ===========================================================================
# Rust (Stream Server) — build, test, lint, audit
# ===========================================================================
rust:
name: Rust (Stream Server)
runs-on: [self-hosted, incus]
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Cache Generated Types
uses: actions/cache@v4
with:
path: apps/web/src/types/generated
key: ${{ runner.os }}-generated-types-${{ hashFiles('veza-backend-api/openapi.yaml') }}
restore-keys: |
${{ runner.os }}-generated-types-
- name: Cache rustup toolchain
id: rustup-cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.rustup
~/.cargo/bin
key: ${{ runner.os }}-rustup-stable-rustfmt-clippy-audit-tarpaulin
save-always: true
- name: Generate Types from OpenAPI
run: |
cd apps/web
chmod +x scripts/generate-types.sh
./scripts/generate-types.sh
continue-on-error: false
# This step ensures types are generated before typecheck
# If types don't match spec, CI will fail
# Cache keyed on openapi.yaml hash, so types regenerate when spec changes
- name: Set up Rust
if: steps.rustup-cache.outputs.cache-hit != 'true'
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --component rustfmt,clippy
- name: Lint
run: npx turbo run lint --filter=veza-frontend
- name: Add ~/.cargo/bin to PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Format Check
run: |
cd apps/web
npm run format:check --if-present
- name: Cache Cargo deps and target
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.cargo/registry
~/.cargo/git
veza-stream-server/target
key: ${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
save-always: true
- name: Type Check
run: |
cd apps/web
npm run typecheck
- name: Build
run: cargo build
working-directory: veza-stream-server
- name: Test
run: npx turbo run test --filter=veza-frontend -- --run
- name: Test
run: cargo test --workspace
working-directory: veza-stream-server
- name: Contrast Tests
run: |
cd apps/web
npm run test -- --run src/__tests__/contrast.test.ts
- name: Clippy
# NOTE: -D warnings temporarily lifted while the team resorbs
# the Rust clippy backlog (~20 warnings: unused imports,
# missing Default impls, manual clamp/contains, etc.).
# Re-enable once the backlog is cleared.
run: cargo clippy --all-targets
working-directory: veza-stream-server
- name: Build
run: npx turbo run build --filter=veza-frontend
- name: Format check
run: cargo fmt -- --check
working-directory: veza-stream-server
storybook:
name: Storybook Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Security audit
# cargo-audit is cached with the rustup toolchain (~/.cargo/bin),
# so the install is a no-op on warm cache.
run: |
command -v cargo-audit >/dev/null || cargo install cargo-audit --locked
cargo audit
working-directory: veza-stream-server
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
# Rust coverage via cargo-tarpaulin is disabled in ci.yml because
# tarpaulin needs CAP_SYS_PTRACE to disable ASLR, which the Docker
# container running the Forgejo act runner doesn't grant:
# "ERROR cargo_tarpaulin: Failed to run tests:
# ASLR disable failed: EPERM: Operation not permitted"
# Either (a) add `privileged: true` to the runner's container
# config to grant ptrace, or (b) switch to `cargo llvm-cov`
# which uses source-based coverage and doesn't need ptrace.
# Until then, run coverage locally or in a dedicated nightly job.
- name: Install dependencies
run: npm ci
# ===========================================================================
# Notify on failure
# ===========================================================================
notify-failure:
name: Notify on failure
needs: [backend, frontend, rust]
- name: Build Storybook
run: npm run build-storybook
working-directory: apps/web
- name: Serve Storybook and run audit
run: |
npx serve -s storybook-static -l 6007 &
for i in $(seq 1 30); do
if curl -sf http://localhost:6007 >/dev/null; then
echo "Storybook ready"
break
fi
sleep 2
done
curl -sf http://localhost:6007 >/dev/null || (echo "Storybook failed to start"; exit 1)
npm run test:storybook
working-directory: apps/web
e2e:
name: E2E (Playwright)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: veza-backend-api/go.sum
- name: Install dependencies
run: npm ci
- name: Add veza.fr to hosts (for Vite proxy)
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
- name: Start backend services (Postgres, Redis, RabbitMQ)
run: |
docker-compose up -d postgres redis rabbitmq
echo "Waiting for Postgres..."
for i in $(seq 1 30); do
if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then
echo "Postgres ready"
break
fi
sleep 2
done
docker-compose ps
- name: Run database migrations
env:
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
run: |
cd veza-backend-api
go run cmd/migrate_tool/main.go
- name: Create E2E test user
env:
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
TEST_EMAIL: e2e@test.com
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
TEST_USERNAME: e2e
run: |
cd veza-backend-api
go run cmd/tools/create_test_user/main.go
- name: Start backend API
env:
APP_ENV: development
APP_PORT: "18080"
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
REDIS_URL: redis://localhost:16379
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET }}
COOKIE_SECURE: "false"
CORS_ALLOWED_ORIGINS: http://veza.fr:5173,http://veza.fr:5174,http://localhost:5173,http://localhost:5174
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "e2e@test.com"
run: |
cd veza-backend-api
go build -o veza-api ./cmd/api/main.go
./veza-api &
sleep 10
curl -f http://localhost:18080/api/v1/health || (echo "Backend health check failed"; exit 1)
- name: Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: apps/web
- name: Run E2E tests
run: npx playwright test
working-directory: apps/web
env:
PORT: "5174"
VITE_API_URL: '/api/v1'
VITE_DOMAIN: veza.fr
VITE_BACKEND_PORT: "18080"
PLAYWRIGHT_BASE_URL: 'http://localhost:5174'
TEST_EMAIL: e2e@test.com
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
- uses: actions/upload-artifact@v4
if: failure()
runs-on: [self-hosted, incus]
steps:
- name: Summary
run: echo "## ❌ CI Failed" >> $GITHUB_STEP_SUMMARY
with:
name: playwright-report
path: apps/web/playwright-report/
retention-days: 7

View file

@ -1,79 +0,0 @@
# cleanup-failed.yml — workflow_dispatch only.
#
# Tears down the kept-alive failed-deploy color (the inactive one
# that survived a Phase D / Phase F failure for forensics).
# Operator triggers this once they have read the journalctl output.
#
# Hard safety in playbooks/cleanup_failed.yml: refuses to destroy
# the currently-active color.
name: Veza cleanup failed-deploy color
on:
workflow_dispatch:
inputs:
env:
description: "Environment to clean up"
required: true
type: choice
options: [staging, prod]
color:
description: "Color to destroy (must NOT be the active one)"
required: true
type: choice
options: [blue, green]
concurrency:
group: cleanup-${{ inputs.env }}
cancel-in-progress: false
jobs:
cleanup:
name: Destroy ${{ inputs.color }} app containers in ${{ inputs.env }}
runs-on: [self-hosted, incus]
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install ansible
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible
ansible-galaxy collection install community.general
- name: Write vault password
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run cleanup_failed.yml
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ inputs.env }}.yml \
playbooks/cleanup_failed.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ inputs.env }} \
-e target_color=${{ inputs.color }}
- name: Upload Ansible log
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}
path: ${{ runner.temp }}/ansible-cleanup-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

84
.github/workflows/container-scan.yml vendored Normal file
View file

@ -0,0 +1,84 @@
name: Container Image Scan
on:
push:
branches: [main]
paths:
- 'veza-backend-api/Dockerfile*'
- 'apps/web/Dockerfile*'
- 'veza-stream-server/Dockerfile*'
pull_request:
branches: [main]
paths:
- 'veza-backend-api/Dockerfile*'
- 'apps/web/Dockerfile*'
- 'veza-stream-server/Dockerfile*'
workflow_dispatch:
jobs:
scan-backend:
name: Scan Backend Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build backend image
run: docker build -t veza-backend:scan -f veza-backend-api/Dockerfile.production veza-backend-api/
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'veza-backend:scan'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
scan-stream-server:
name: Scan Stream Server Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build stream server image
run: docker build -t veza-stream:scan -f veza-stream-server/Dockerfile .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'veza-stream:scan'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
scan-frontend:
name: Scan Frontend Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check if frontend Dockerfile exists
id: check
run: |
if [ -f "apps/web/Dockerfile" ] || [ -f "apps/web/Dockerfile.production" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Build frontend image
if: steps.check.outputs.exists == 'true'
run: |
DOCKERFILE=$([ -f "apps/web/Dockerfile.production" ] && echo "apps/web/Dockerfile.production" || echo "apps/web/Dockerfile")
docker build -t veza-frontend:scan -f "$DOCKERFILE" apps/web/
- name: Run Trivy vulnerability scanner
if: steps.check.outputs.exists == 'true'
uses: aquasecurity/trivy-action@master
with:
image-ref: 'veza-frontend:scan'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true

View file

@ -1,360 +0,0 @@
# Veza deploy pipeline.
#
# Triggers (intentionally narrow — see SECURITY note below):
# workflow_dispatch → operator-supplied env + sha
# (push:main + tag:v* are commented OUT until provisioning is
# complete — see docs/RUNBOOK_DEPLOY_BOOTSTRAP.md. Re-enable
# once secrets/runner/vault are in place and a manual run via
# workflow_dispatch has been verified GREEN.)
#
# SECURITY: this workflow runs on a self-hosted runner with access to
# the Incus unix socket (effectively root on the host). DO NOT add
# `pull_request` or any fork-influenced trigger here — an attacker-
# controlled fork would be able to `incus exec` arbitrarily. The
# narrow trigger list above is the security boundary.
#
# Sequence : build (3 jobs in parallel) → upload artifacts → deploy.
name: Veza deploy
on:
# push: # GATED — uncomment after first
# branches: [main] # successful workflow_dispatch run
# tags: ['v*'] # see RUNBOOK_DEPLOY_BOOTSTRAP.md
workflow_dispatch:
inputs:
env:
description: "Environment to deploy"
required: true
default: staging
type: choice
options: [staging, prod]
release_sha:
description: "Full git SHA to deploy (defaults to current HEAD if empty)"
required: false
type: string
concurrency:
# Only one deploy per env at a time. Newer pushes cancel older
# in-flight builds for the same env (the user almost always wants
# the newer commit).
group: deploy-${{ github.ref_type == 'tag' && 'prod' || 'staging' }}
cancel-in-progress: true
env:
# Where build artefacts land. Set in Forgejo repo Variables :
# FORGEJO_REGISTRY_URL = https://forgejo.veza.fr/api/packages/talas/generic
REGISTRY_URL: ${{ vars.FORGEJO_REGISTRY_URL }}
jobs:
# =================================================================
# Resolve env + sha from the trigger.
# =================================================================
resolve:
name: Resolve env + SHA
runs-on: [self-hosted, incus]
outputs:
env: ${{ steps.r.outputs.env }}
sha: ${{ steps.r.outputs.sha }}
steps:
- name: Resolve
id: r
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
ENV="${{ inputs.env }}"
SHA="${{ inputs.release_sha || github.sha }}"
elif [ "${{ github.ref_type }}" = "tag" ]; then
ENV="prod"
SHA="${{ github.sha }}"
else
ENV="staging"
SHA="${{ github.sha }}"
fi
if ! echo "$SHA" | grep -Eq '^[0-9a-f]{40}$'; then
echo "SHA '$SHA' is not a 40-char git SHA"
exit 1
fi
echo "env=$ENV" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "Resolved env=$ENV sha=$SHA"
# =================================================================
# Build backend (Go).
# =================================================================
build-backend:
name: Build backend
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
cache-dependency-path: veza-backend-api/go.sum
- name: Test
working-directory: veza-backend-api
env:
VEZA_SKIP_INTEGRATION: "1"
run: go test ./... -short -count=1 -timeout 300s
- name: Build veza-api (CGO=0, static)
working-directory: veza-backend-api
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
run: |
go build -trimpath -ldflags "-s -w" \
-o ./bin/veza-api ./cmd/api/main.go
go build -trimpath -ldflags "-s -w" \
-o ./bin/migrate_tool ./cmd/migrate_tool/main.go
- name: Stage tarball contents
working-directory: veza-backend-api
run: |
STAGE="$RUNNER_TEMP/veza-backend"
mkdir -p "$STAGE/migrations"
cp ./bin/veza-api ./bin/migrate_tool "$STAGE/"
cp -r ./migrations/* "$STAGE/migrations/" || true
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-backend" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-backend/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Build stream (Rust).
# =================================================================
build-stream:
name: Build stream
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Set up Rust toolchain
run: |
command -v rustup >/dev/null || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source "$HOME/.cargo/env"
rustup target add x86_64-unknown-linux-musl
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
sudo apt-get update -qq && sudo apt-get install -y musl-tools
- name: Cache cargo + target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
veza-stream-server/target
key: deploy-${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
restore-keys: |
deploy-${{ runner.os }}-cargo-
- name: Test
working-directory: veza-stream-server
run: cargo test --workspace
- name: Build stream_server (musl static)
working-directory: veza-stream-server
run: |
cargo build --release --locked \
--target x86_64-unknown-linux-musl
- name: Stage tarball contents
working-directory: veza-stream-server
run: |
STAGE="$RUNNER_TEMP/veza-stream"
mkdir -p "$STAGE"
cp ./target/x86_64-unknown-linux-musl/release/stream_server "$STAGE/"
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-stream" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-stream/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Build web (React/Vite).
# =================================================================
build-web:
name: Build web
needs: resolve
runs-on: [self-hosted, incus]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Build design tokens
run: npm run build:tokens --workspace=@veza/design-system
- name: Build SPA
working-directory: apps/web
env:
VITE_API_URL: /api/v1
VITE_DOMAIN: ${{ needs.resolve.outputs.env == 'prod' && 'veza.fr' || 'staging.veza.fr' }}
VITE_RELEASE_SHA: ${{ needs.resolve.outputs.sha }}
run: npm run build
- name: Stage tarball contents
run: |
STAGE="$RUNNER_TEMP/veza-web"
mkdir -p "$STAGE"
cp -r apps/web/dist/* "$STAGE/"
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
- name: Pack tarball
run: |
cd "$RUNNER_TEMP"
tar --use-compress-program=zstd -cf \
"veza-web-${{ needs.resolve.outputs.sha }}.tar.zst" \
-C "$RUNNER_TEMP/veza-web" .
- name: Push to Forgejo Package Registry
env:
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
run: |
set -e
TARBALL="veza-web-${{ needs.resolve.outputs.sha }}.tar.zst"
URL="${REGISTRY_URL}/veza-web/${{ needs.resolve.outputs.sha }}/${TARBALL}"
echo "PUT → $URL"
curl -fsSL --fail-with-body -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$RUNNER_TEMP/${TARBALL}" \
"${URL}"
# =================================================================
# Deploy via Ansible. Runs on the self-hosted runner that has
# Incus socket access (label `incus`). Requires Forgejo secrets:
# ANSIBLE_VAULT_PASSWORD — unlocks group_vars/all/vault.yml
# FORGEJO_REGISTRY_TOKEN — same token the build jobs use,
# passed to ansible-playbook so
# the data containers can fetch
# the tarballs they were just sent.
# =================================================================
deploy:
name: Deploy via Ansible
needs: [resolve, build-backend, build-stream, build-web]
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ needs.resolve.outputs.sha }}
- name: Install ansible + community.general + community.postgresql + community.rabbitmq
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible python3-psycopg2 python3-pip
ansible-galaxy collection install \
community.general \
community.postgresql \
community.rabbitmq
- name: Write vault password to a tmpfile
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run deploy_data.yml (idempotent provisioning + ZFS snapshot)
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-data-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ needs.resolve.outputs.env }}.yml \
playbooks/deploy_data.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ needs.resolve.outputs.env }} \
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Run deploy_app.yml (blue/green)
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-app-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible-playbook \
-i inventory/${{ needs.resolve.outputs.env }}.yml \
playbooks/deploy_app.yml \
--vault-password-file "$VAULT_PASS_FILE" \
-e veza_env=${{ needs.resolve.outputs.env }} \
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Upload Ansible logs (for forensics)
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-logs-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}
path: ${{ runner.temp }}/ansible-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

View file

@ -1,270 +0,0 @@
name: E2E Playwright
# v1.0.8 Batch C — Playwright E2E suite triggered on PRs (@critical only,
# fast feedback) + push to main and nightly (full suite, deeper coverage).
# Uses the --ci seed flag (cmd/tools/seed --ci) for ~5s seeding instead
# of the ~60s minimal seed.
on:
# GATED on Forgejo (single self-hosted runner) — re-enable
# selectively when an additional runner with a Docker label
# (e.g. ubuntu-latest:docker://...) is provisioned. Until then,
# heavy E2E only runs on operator-triggered workflow_dispatch.
# pull_request:
# branches: [main]
# push:
# branches: [main]
# schedule:
# - cron: "0 3 * * *"
workflow_dispatch:
env:
GIT_SSL_NO_VERIFY: "true"
NODE_TLS_REJECT_UNAUTHORIZED: "0"
# Forces playwright.config.ts:141,155 to spawn fresh backend + Vite
# instead of reusing whatever is on the runner.
CI: "true"
# Falls back to a CI-only dev key if the Forgejo secret is unset.
# Used at the "Build + start backend API" step.
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET || 'ci-dev-jwt-secret-32-chars-min-padding!!' }}
jobs:
# ===========================================================================
# Job: e2e — single matrix entry that selects the test scope per trigger.
# - PR → @critical only (5-7min target)
# - push main / cron / dispatch → full suite (~25min target)
# ===========================================================================
e2e:
# Scope matrix:
# - pull_request → @critical (PR gate, ~5-10min)
# - push to main → @critical (commit gate, dev velocity priority)
# - schedule (cron) → full suite (nightly coverage)
# - workflow_dispatch → full (manual broad sweep)
# Push was previously running the full suite (~1h30 pre-perf, ~15-20min
# post-perf). The dev velocity cost was unjustifiable for the
# incremental coverage over the @critical scope, especially while the
# full suite carries pre-existing fixme'd tests. Cron picks up the
# rest on a 24h cadence.
name: e2e (${{ (github.event_name == 'pull_request' || github.event_name == 'push') && '@critical' || 'full' }})
runs-on: [self-hosted, incus]
timeout-minutes: ${{ (github.event_name == 'pull_request' || github.event_name == 'push') && 20 || 45 }}
# Service containers are managed by act_runner: spawned on the job
# network with healthchecks, torn down at the end. This replaces
# the previous `docker compose up -d` pattern which relied on
# docker socket sharing + host port mappings — fragile (port
# collisions across concurrent jobs, manual cleanup, double-DinD,
# whole compose file validated even when only 3 services are
# needed). Service hostnames (`postgres`, `redis`, `rabbitmq`)
# resolve from the job container on standard ports.
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: veza
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: veza
options: >-
--health-cmd "pg_isready -U veza"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
# No-auth redis for CI: act_runner services don't support a
# `command:` field, and the redis:7-alpine entrypoint does
# NOT read REDIS_ARGS (verified empirically) — so passing
# --requirepass via env doesn't work. The dev/prod password
# policy (REM-023) is enforced via docker-compose.yml only;
# the CI service network is ephemeral and isolated, so
# dropping auth here is acceptable.
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
rabbitmq:
image: rabbitmq:3-management-alpine
env:
RABBITMQ_DEFAULT_USER: veza
RABBITMQ_DEFAULT_PASS: devpassword
options: >-
--health-cmd "rabbitmq-diagnostics -q check_port_connectivity"
--health-interval 10s
--health-timeout 5s
--health-retries 10
# Service hostnames + standard ports — no host-port mapping needed.
env:
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@postgres:5432/veza?sslmode=disable
REDIS_URL: redis://redis:6379
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL || 'amqp://veza:devpassword@rabbitmq:5672/' }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
with:
go-version: "1.25"
cache: true
cache-dependency-path: veza-backend-api/go.sum
- name: Install dependencies
run: npm ci
# Sprint 2 design-system migrated to Style Dictionary; the
# generated tokens live in packages/design-system/dist/
# (gitignored). The Playwright-spawned Vite imports them via
# `@veza/design-system/tokens-generated`, so dist/ MUST exist
# before vite starts.
- name: Build design tokens
run: npm run build:tokens --workspace=@veza/design-system
# Playwright tests reach the frontend via http://veza.fr:5174,
# which the browsers resolve via /etc/hosts. Without this entry
# the navigation step times out.
- name: Add veza.fr to hosts
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
- name: Generate dev JWT keys + SSL cert
run: |
./scripts/generate-jwt-keys.sh
./scripts/generate-ssl-cert.sh
- name: Run database migrations
run: |
cd veza-backend-api
go run cmd/migrate_tool/main.go
- name: Seed database (CI mode — 5 test accounts + minimal fixtures)
run: |
cd veza-backend-api
go run ./cmd/tools/seed --ci
- name: Build + start backend API
env:
APP_ENV: test
APP_PORT: "18080"
COOKIE_SECURE: "false"
CORS_ALLOWED_ORIGINS: http://veza.fr:5174,http://localhost:5174
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
RATE_LIMIT_LIMIT: "10000"
RATE_LIMIT_WINDOW: "60"
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "user@veza.music,artist@veza.music,admin@veza.music,mod@veza.music,new@veza.music"
run: |
cd veza-backend-api
go build -o veza-api ./cmd/api/main.go
./veza-api > /tmp/backend.log 2>&1 &
BACKEND_PID=$!
# Poll for up to 30s — beats a fixed sleep on a cold start.
for i in $(seq 1 30); do
if curl -sf -m 2 http://localhost:18080/api/v1/health > /tmp/health.json 2>/dev/null; then
break
fi
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
echo "::error::backend process died before becoming reachable"
echo "--- /tmp/backend.log (last 200 lines) ---"
tail -200 /tmp/backend.log
exit 1
fi
sleep 1
done
# Always print the response body so debugging doesn't
# require re-running with extra logging. Artifact upload
# is broken under Forgejo (GHES not supported), so the
# log step output is our only diagnostic channel.
echo "--- /api/v1/health response ---"
cat /tmp/health.json
echo
# The /api/v1/health envelope is the standard veza response
# shape: {"success": true, "data": {"status": "ok"}}. Earlier
# versions of this check used `.status == "ok"` at the root,
# which silently misses the actual ok signal nested under
# `.data`. The misread surfaced as "backend health is not ok"
# despite a 200 + valid body — wasted a CI cycle.
if ! jq -e '.data.status == "ok"' /tmp/health.json >/dev/null; then
echo "::error::backend health is not ok"
echo "--- /tmp/backend.log (last 200 lines) ---"
tail -200 /tmp/backend.log
exit 1
fi
echo "Backend healthy"
# Cache the Playwright browser binaries between runs.
# Chromium download is ~150MB and adds 30-60s to every cold
# run. The cache key tracks the playwright version pinned in
# package-lock.json, so a Playwright bump invalidates the
# cache automatically.
- name: Resolve Playwright version
id: playwright-version
run: |
PV=$(node -p "require('./node_modules/@playwright/test/package.json').version")
echo "version=$PV" >> $GITHUB_OUTPUT
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-chromium
restore-keys: |
playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-
- name: Install Playwright browsers
# Browsers cached: only install OS deps (apt-get sweep) so the
# download is skipped. Browsers absent: full install + deps.
run: |
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = "true" ]; then
npx playwright install-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: Run E2E (@critical — PR + push)
if: github.event_name == 'pull_request' || github.event_name == 'push'
env:
PORT: "5174"
VITE_API_URL: "/api/v1"
VITE_DOMAIN: veza.fr
VITE_BACKEND_PORT: "18080"
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
run: npm run e2e:critical
- name: Run E2E (full — cron / workflow_dispatch)
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
env:
PORT: "5174"
VITE_API_URL: "/api/v1"
VITE_DOMAIN: veza.fr
VITE_BACKEND_PORT: "18080"
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
run: npm run e2e
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: playwright-report-${{ github.run_id }}-${{ github.run_attempt }}
path: |
tests/e2e/playwright-report/
tests/e2e/test-results/
retention-days: 7
- name: Upload backend log
if: failure()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: backend-log-${{ github.run_id }}-${{ github.run_attempt }}
path: /tmp/backend.log
retention-days: 7

48
.github/workflows/frontend-ci.yml vendored Normal file
View file

@ -0,0 +1,48 @@
name: Frontend CI
on:
push:
paths:
- "apps/web/**"
- ".github/workflows/frontend-ci.yml"
pull_request:
paths:
- "apps/web/**"
- ".github/workflows/frontend-ci.yml"
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: 'npm'
cache-dependency-path: apps/web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: TypeScript check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Audit dependencies
run: npm audit --audit-level=critical
- name: Run tests
run: npm run test -- --run

View file

@ -1,43 +0,0 @@
name: Go Fuzz Tests
on:
# GATED — operator-triggered until extra runner capacity exists.
# schedule:
# - cron: "0 2 * * *" # Nightly at 2am UTC
workflow_dispatch:
env:
GIT_SSL_NO_VERIFY: "true"
NODE_TLS_REJECT_UNAUTHORIZED: "0"
jobs:
fuzz:
runs-on: [self-hosted, incus]
timeout-minutes: 15
defaults:
run:
working-directory: veza-backend-api
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
with:
go-version: "1.25"
cache: true
- name: Download deps
run: go mod download
- name: Run fuzz tests
run: go test -fuzz=Fuzz -fuzztime=60s ./internal/handlers/...
- name: Upload fuzz corpus
if: always()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: fuzz-corpus
path: veza-backend-api/testdata/fuzz/
retention-days: 30

81
.github/workflows/load-test-nightly.yml vendored Normal file
View file

@ -0,0 +1,81 @@
name: Load Tests (Nightly)
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install k6
run: |
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install -y k6
- name: Start infrastructure
run: |
docker-compose -f docker-compose.yml up -d postgres redis rabbitmq
sleep 15
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
cache: true
- name: Run migrations
working-directory: veza-backend-api
env:
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
REDIS_URL: redis://localhost:16379
JWT_SECRET: test-jwt-secret-for-load-test
APP_ENV: test
run: |
go mod download
go run cmd/migrate_tool/main.go || true
- name: Start backend API
working-directory: veza-backend-api
env:
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
REDIS_URL: redis://localhost:16379
RABBITMQ_URL: amqp://veza:devpassword@localhost:15672/
JWT_SECRET: test-jwt-secret-for-load-test
APP_ENV: test
PORT: 8080
run: |
go run cmd/api/main.go &
sleep 15
- name: Wait for backend
run: |
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -sf http://localhost:8080/health; then
echo "Backend ready"
exit 0
fi
sleep 3
done
echo "Backend not ready"
exit 1
- name: Run smoke load test
run: k6 run loadtests/smoke.js
- name: Run backend load test
run: |
k6 run --out json=load-results.json loadtests/backend/full.js || true
continue-on-error: true
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: load-test-results
path: load-results.json
if: always()

View file

@ -1,126 +0,0 @@
name: k6 nightly load test
# v1.0.9 W4 Day 20 — runs the mixed-scenarios k6 script against the
# staging environment every night at 02:30 UTC. The acceptance gate
# is "pass green 3 nuits consécutives" before flipping a release —
# the artifact uploaded by this workflow carries the JSON summary
# the operator inspects.
#
# Scope deliberately narrow : runs ONLY on staging, NEVER on prod.
# A separate manually-triggered workflow (workflow_dispatch) covers
# pre-launch capacity drills with a longer ramp.
on:
# GATED — k6 hammer is too heavy for the single self-hosted runner.
# Re-enable the cron once a dedicated load-test runner exists.
# schedule:
# - cron: "30 2 * * *"
workflow_dispatch:
inputs:
duration:
description: "Duration per scenario (e.g. 5m, 15m, 1h)"
required: false
default: "5m"
type: string
base_url:
description: "Override staging URL"
required: false
default: ""
type: string
env:
GIT_SSL_NO_VERIFY: "true"
# Defaults — override via workflow_dispatch input or repo vars.
DEFAULT_BASE_URL: "https://staging.veza.fr"
jobs:
loadtest:
name: k6 mixed scenarios (1650 VU steady)
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install k6
run: |
set -euo pipefail
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
| sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install -y k6
k6 version
- name: Resolve test inputs
id: inputs
run: |
set -euo pipefail
BASE_URL="${{ github.event.inputs.base_url }}"
if [ -z "$BASE_URL" ]; then
BASE_URL="${{ vars.STAGING_BASE_URL || env.DEFAULT_BASE_URL }}"
fi
DURATION="${{ github.event.inputs.duration }}"
if [ -z "$DURATION" ]; then
DURATION="5m"
fi
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
echo "duration=$DURATION" >> "$GITHUB_OUTPUT"
- name: Pre-flight — staging is reachable
run: |
set -euo pipefail
url="${{ steps.inputs.outputs.base_url }}/api/v1/health"
echo "::notice::Pre-flight GET $url"
status=$(curl -k -sS --max-time 10 -o /dev/null -w "%{http_code}" "$url" || echo "000")
if [ "$status" != "200" ]; then
echo "::error::Staging /health returned $status — aborting load test."
exit 1
fi
- name: Run k6 mixed scenarios
id: run
env:
BASE_URL: ${{ steps.inputs.outputs.base_url }}
DURATION: ${{ steps.inputs.outputs.duration }}
USER_TOKEN: ${{ secrets.STAGING_LOADTEST_TOKEN }}
STREAM_TRACK_ID: ${{ vars.STAGING_LOADTEST_TRACK_ID || '00000000-0000-0000-0000-000000000001' }}
run: |
set -euo pipefail
if [ -z "$USER_TOKEN" ]; then
echo "::warning::STAGING_LOADTEST_TOKEN secret is empty — auth-required scenarios will record 401s as errors."
fi
k6 run --quiet \
--summary-export=k6-summary.json \
scripts/loadtest/k6_mixed_scenarios.js
- name: Upload k6 summary artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: k6-summary-${{ github.run_number }}
path: |
k6-summary.json
scripts/loadtest/k6_mixed_scenarios.js
retention-days: 30
- name: Annotate thresholds in summary
if: always()
run: |
set -euo pipefail
if [ ! -f k6-summary.json ]; then
echo "::warning::No summary artifact — k6 likely failed before write."
exit 0
fi
echo "## k6 load test summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
jq -r '
(.metrics.http_reqs.values.count // 0) as $reqs
| (.metrics.http_req_failed.values.rate // 0) as $err
| (.metrics.http_req_duration.values["p(95)"] // 0) as $p95
| (.metrics.http_req_duration.values["p(99)"] // 0) as $p99
| "- requests: \($reqs)\n- failed rate: \($err * 100 | round)/100 %\n- p95: \($p95 | round) ms\n- p99: \($p99 | round) ms"
' k6-summary.json >> "$GITHUB_STEP_SUMMARY"

View file

@ -1,118 +0,0 @@
# rollback.yml — workflow_dispatch only.
#
# Two modes :
# fast — flip HAProxy back to the previous color. ~5s. Requires
# the target color's containers to still be alive
# (i.e., no later deploy has recycled them).
# full — re-run deploy_app.yml with a specific (older) release_sha.
# ~5-10min. The artefact must still be in the Forgejo
# registry (default retention 30 SHA per component).
#
# See docs/RUNBOOK_ROLLBACK.md for decision criteria.
name: Veza rollback
on:
workflow_dispatch:
inputs:
env:
description: "Environment to rollback"
required: true
type: choice
options: [staging, prod]
mode:
description: "Rollback mode"
required: true
type: choice
options: [fast, full]
target_color:
description: "(mode=fast only) color to flip back TO (the prior active one)"
required: false
type: choice
options: [blue, green]
release_sha:
description: "(mode=full only) 40-char SHA of the release to redeploy"
required: false
type: string
concurrency:
group: rollback-${{ inputs.env }}
cancel-in-progress: false
jobs:
rollback:
name: Rollback ${{ inputs.env }} (${{ inputs.mode }})
runs-on: [self-hosted, incus]
timeout-minutes: 30
steps:
- name: Validate inputs
run: |
if [ "${{ inputs.mode }}" = "fast" ] && [ -z "${{ inputs.target_color }}" ]; then
echo "mode=fast requires target_color"
exit 1
fi
if [ "${{ inputs.mode }}" = "full" ]; then
if [ -z "${{ inputs.release_sha }}" ]; then
echo "mode=full requires release_sha"
exit 1
fi
if ! echo "${{ inputs.release_sha }}" | grep -Eq '^[0-9a-f]{40}$'; then
echo "release_sha is not a 40-char git SHA"
exit 1
fi
fi
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ inputs.mode == 'full' && inputs.release_sha || github.ref }}
- name: Install ansible + collections
run: |
sudo apt-get update -qq
sudo apt-get install -y ansible python3-psycopg2
ansible-galaxy collection install \
community.general \
community.postgresql \
community.rabbitmq
- name: Write vault password
env:
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
chmod 0400 "$RUNNER_TEMP/vault-pass"
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
- name: Run rollback.yml
working-directory: infra/ansible
env:
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}.log
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
EXTRA="-e veza_env=${{ inputs.env }} -e mode=${{ inputs.mode }}"
if [ "${{ inputs.mode }}" = "fast" ]; then
EXTRA="$EXTRA -e target_color=${{ inputs.target_color }}"
else
EXTRA="$EXTRA -e veza_release_sha=${{ inputs.release_sha }}"
EXTRA="$EXTRA -e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}"
fi
ansible-playbook \
-i inventory/${{ inputs.env }}.yml \
playbooks/rollback.yml \
--vault-password-file "$VAULT_PASS_FILE" \
$EXTRA
- name: Upload Ansible log
if: always()
uses: actions/upload-artifact@v4
with:
name: ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}
path: ${{ runner.temp }}/ansible-rollback-*.log
retention-days: 30
- name: Shred vault password file
if: always()
run: |
if [ -f "$VAULT_PASS_FILE" ]; then
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
fi

22
.github/workflows/rust-ci.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Rust CI
on:
push:
branches: [main]
paths:
- 'veza-stream-server/**'
pull_request:
branches: [main]
paths:
- 'veza-stream-server/**'
jobs:
clippy-stream:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Clippy lint
run: cargo clippy -- -D warnings
working-directory: veza-stream-server

22
.github/workflows/sast.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: CodeQL SAST
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
matrix:
language: [go, javascript-typescript]
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3

View file

@ -1,28 +1,22 @@
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
GIT_SSL_NO_VERIFY: "true"
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
gitleaks:
name: Secret Scanning (gitleaks)
runs-on: [self-hosted, incus]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
gitleaks:
name: Secret Scanning (gitleaks)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install gitleaks
run: |
wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz
tar xzf gitleaks_8.21.2_linux_x64.tar.gz
chmod +x gitleaks
- name: Run gitleaks
run: ./gitleaks detect --source . --no-banner -v --config .gitleaks.toml
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

47
.github/workflows/storybook-audit.yml vendored Normal file
View file

@ -0,0 +1,47 @@
# Storybook audit: build static Storybook, serve it, run the audit script.
# Fails the job if any story has console errors, page errors, or unhandled network failures.
# See docs/STORYBOOK_CONTRACT.md and apps/web/scripts/audit-storybook.js.
name: Storybook Audit
on:
push:
paths:
- "apps/web/**"
- ".github/workflows/storybook-audit.yml"
pull_request:
paths:
- "apps/web/**"
- ".github/workflows/storybook-audit.yml"
workflow_dispatch:
jobs:
audit:
name: Build & audit Storybook
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: apps/web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps
- name: Validate Storybook (build, serve 6007, audit)
run: npm run validate:storybook
env:
VITE_API_URL: /api/v1
VITE_USE_MSW: "true"
VITE_STORYBOOK: "true"

41
.github/workflows/stream-ci.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Stream Server CI
on:
push:
paths:
- "veza-stream-server/**"
- "veza-common/**"
- ".github/workflows/stream-ci.yml"
pull_request:
paths:
- "veza-stream-server/**"
- "veza-common/**"
- ".github/workflows/stream-ci.yml"
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: veza-stream-server
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Lint with clippy
run: cargo clippy --all-targets -- -D warnings
- name: Audit dependencies
uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests
run: cargo test --all

View file

@ -1,24 +0,0 @@
name: Trivy Filesystem Scan
on:
pull_request:
branches: [main]
workflow_dispatch:
env:
GIT_SSL_NO_VERIFY: "true"
jobs:
trivy-scan:
name: Trivy FS Scan
runs-on: [self-hosted, incus]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Trivy
run: |
wget -qO- https://github.com/aquasecurity/trivy/releases/download/v0.58.1/trivy_0.58.1_Linux-64bit.tar.gz | tar xz
chmod +x trivy
- name: Scan filesystem
run: ./trivy fs --severity HIGH,CRITICAL --exit-code 1 .

170
.gitignore vendored
View file

@ -36,11 +36,6 @@ logs/
*.seed
*.gz
### Database dumps — SECURITY(REM-034): Never commit database artifacts
**/veza_back_api_db/
*.sql.dump
*.pgdump
### Editors / IDE
.vscode/
.idea/
@ -83,12 +78,10 @@ apps/web/dist_verification/
.env
.env.*
!.env.example
!.env.staging.example
**/.env
**/.env.local
**/.env.*
!.env.example
!.env.staging.example
veza-backend-api/.env
veza-chat-server/.env
veza-stream-server/.env
@ -99,35 +92,15 @@ apps/web/.env.local
docker-data/
*.tar
# HAProxy SSL certs (never commit private keys or full-chain certs)
# HAProxy SSL certs (never commit private keys)
docker/haproxy/certs/*.key
docker/haproxy/certs/*.pem
docker/haproxy/certs/*.crt
# JWT RSA keys (v0.9.1 RS256 migration — NEVER commit)
jwt-private.pem
jwt-public.pem
veza-backend-api/main
veza-backend-api/api
veza-backend-api/veza-api
veza-backend-api/migrate_tool
chat_exports/
# Debug/test screenshots (root level)
screenshot-*.png
sidebar-*.png
player-*.png
login-*.png
search-*.png
track-*.png
test-*.png
dashboard-*.png
report-*.html
# MCP config (local)
.mcp.json
# Environment / Secrets — config templates only, never commit real .env
config/incus/env/*.env
!config/incus/env/env.example
@ -135,150 +108,11 @@ config/incus/env/*.env
# Playwright
/test-results/
/playwright-report/
tests/e2e/test-results/
tests/e2e/VEZA_AUDIT_REPORT.html
tests/e2e/VEZA_AUDIT_REPORT.json
apps/web/e2e-results.json
e2e-results.json
/blob-report/
/playwright/.cache/
/playwright/.auth/
apps/web/e2e/.auth/
*storybook.log
storybook-static
# v0.941: Swagger docs.go generated by CI (swag init)
veza-backend-api/docs/docs.go
# Claude Code local memory
.claude/
# Test audio files (large binaries)
veza-backend-api/audio/
# SELinux policy (local)
qemu-fusefs.*
# Root-level 'api' binary produced by `go build` in veza-backend-api/.
# Narrower than the previous bare `api` rule which matched any file or
# directory named 'api' anywhere (including apps/web/src/services/api/).
/api
/veza-backend-api/api
# ============================================================
# Post-audit J1 (2026-04-14) — never recommit this debris
# ============================================================
# Go binaries accidentally committed (v1.0.3 → v1.0.4 cleanup)
veza-backend-api/server
veza-backend-api/modern-server
veza-backend-api/seed
veza-backend-api/seed-v2
veza-backend-api/encrypt_oauth_tokens
# Coverage reports (generated, never tracked)
veza-backend-api/coverage*.out
veza-backend-api/coverage_groups/
# Frontend build/lint/test artifacts
apps/web/lint_report*.json
apps/web/tsc*.log
apps/web/tsc*.txt
apps/web/ts_*.log
apps/web/storybook_*.json
apps/web/debug-storybook.log
apps/web/build_errors*.txt
apps/web/build_output.txt
apps/web/final_errors.txt
apps/web/*.log
apps/web/diagnostic-*.log
apps/web/frontend.log
apps/web/audit.log
# Backend local logs
veza-backend-api/backend*.log
# Root audit screenshots (belong in docs/assets/ if needed)
/audit-*.png
# AI tooling session state (not code)
.cursor/
# ============================================================
# Post-audit J2 (2026-04-20) — branch chore/v1.0.7-cleanup
# ============================================================
# Tracked audio fixtures — use git-lfs or fixtures repo, never commit raw audio
veza-backend-api/uploads/
# TLS/SSL certificates committed pre-2026-04 (regen with scripts/generate-ssl-cert.sh)
config/ssl/*.pem
config/ssl/*.key
config/ssl/*.crt
# Playwright MCP session debris
.playwright-mcp/
# AI session artefacts / context dumps
CLAUDE_CONTEXT.txt
UI_CONTEXT_SUMMARY.md
*.context.txt
*.ai-session.txt
# One-off generated tooling scripts (should live in scripts/ if kept)
/generate_page_fix_prompts.sh
/build-archive.log
# Apps/web stale audit reports (generated, never tracked)
apps/web/AUDIT_ISSUES.json
apps/web/audit_remediation.json
apps/web/lint_comprehensive.json
apps/web/storybook-roadmap.json
apps/web/storybook-*.json
# Root PNG screenshots — move to docs/screenshots/ if historical value
/design-system-*.png
/forgot-password-*.png
/register-*.png
/reset-password-*.png
/settings-*.png
/storybook-*.png
# ============================================================
# Post-audit J3 (2026-04-23) — history rewrite (BFG pass, 1.5G → 66M)
# ============================================================
# Additional Go build artifacts found in BFG scan
veza-backend-api/bin/
veza-backend-api/veza-backend-api
veza-backend-api/migrate
# Vendored binaries mistakenly committed
dev-environment/scripts/kubectl
# Incus build outputs (generated per release cut)
.build/
# E2E report outputs (Playwright)
tests/e2e/audit/results/
tests/e2e/playwright-report/
# Session-scratch screenshots
frontend_screenshots/
# Audit_remediation glob (supersedes J2's exact-match json)
apps/web/audit_remediation*
# ============================================================
# Ansible Vault — secrets at rest stay encrypted in vault.yml
# (committed). The vault password used to unlock them MUST NOT
# be committed; the Forgejo runner reads it from a repo secret.
# ============================================================
infra/ansible/.vault-pass
infra/ansible/.vault-pass.*
# Local copies devs sometimes drop next to the repo for editing
.vault-pass
.vault-pass.*
# ============================================================
# Bootstrap scripts — local config + state stay out of git
# ============================================================
scripts/bootstrap/.env
.git/talas-bootstrap/

View file

@ -1,79 +0,0 @@
title = "Veza gitleaks config"
# Inherit gitleaks v8 default ruleset
[extend]
useDefault = true
# Project-wide allowlist
#
# Categories of allowed paths (every entry below is a known false-positive
# source confirmed by reading the file or its history):
#
# 1. Go test files — fake JWTs like eyJ...invalid_signature for auth-failure tests
# 2. Historical .backup-pre-uuid-migration dir — gone from HEAD but in git history
# 3. Playwright e2e artifacts — auth state snapshots, test result dumps
# 4. Storybook stories + MSW mocks — UI fixtures with placeholder API keys
# 5. Documentation — API examples, smoke test logs, integration guides
# 6. K8s deployment templates — base64-encoded "secure_pass" placeholders
# 7. Local dev TLS certs (CN=localhost) under docker/haproxy/certs/
# 8. Rust/TS test fixtures — deterministic constants used only in #[cfg(test)]
# 9. Generated bundle analysis HTML
# 10. Legacy templates (apps/web/desy/legacy/)
#
# This allowlist intentionally errs on the side of letting things through.
# Real secret rotation should rely on .env, vault, or k8s sealed-secrets.
# When tightening, prefer adding a stopword over removing a path entry.
[allowlist]
description = "Allowlist test fixtures, docs, k8s templates, and dev artifacts"
paths = [
# Go tests
'''.*_test\.go$''',
'''.*\.backup-pre-uuid-migration/.*''',
'''veza-backend-api/internal/services/\.backup-pre-uuid-migration/.*''',
# Playwright / e2e artifacts
'''apps/web/e2e/\.auth/.*''',
'''apps/web/e2e-results\.json$''',
'''apps/web/full_test_result\.txt$''',
'''apps/web/e2e/.*\.md$''',
# Storybook + MSW mocks
'''apps/web/.*\.stories\.(ts|tsx|js|jsx)$''',
'''apps/web/src/mocks/.*''',
# Documentation (markdown samples are inherently full of example tokens)
'''.*\.md$''',
# K8s deployment templates with base64 placeholders
'''.*/k8s/.*\.ya?ml$''',
# Local dev / self-signed TLS material
'''docker/haproxy/certs/.*\.(pem|key|crt|csr)$''',
# Rust / TS test fixtures inside source files (constants used only in
# #[cfg(test)] modules — see veza-stream-server/src/utils/signature.rs)
'''veza-stream-server/src/utils/signature\.rs$''',
'''veza-stream-server/src/utils/env\.rs$''',
'''veza-chat-server/src/env\.rs$''',
# Legacy / static templates
'''apps/web/desy/legacy/.*''',
# Pre-existing source files with hardcoded *test* keys (must stay until refactor)
'''apps/web/src/components/studio/.*''',
'''apps/web/src/components/settings/security/TwoFactorSetup\.tsx$''',
'''apps/web/src/features/live/.*''',
# Generated artifacts
'''\.build/.*\.html$''',
]
stopwords = [
"invalid_signature",
"test-jwt-secret",
"test-secret",
"test-internal-api-key",
"test_secret_key_that_is_long_enough_32chars",
"sk-abc123-def456-ghi789",
"live_83921_abc123xyz789_secret_key",
"secure_pass",
]

View file

@ -1,3 +0,0 @@
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"

View file

@ -1,34 +1,20 @@
#!/usr/bin/env sh
# Each step runs in a subshell so the cd does not leak across steps.
# Pre-commit runs from the repo root; every cd below is relative to that.
# Drift guard: ensure apps/web/src/services/generated/ (orval) matches
# veza-backend-api/openapi.yaml. Regenerates locally then fails if the
# committed types don't match the freshly-regenerated output.
# Skip with SKIP_TYPES=1 for emergency commits (documented in CLAUDE.md).
if [ -z "$SKIP_TYPES" ]; then
(cd apps/web && bash scripts/check-types-sync.sh) || {
echo "❌ OpenAPI types are out of sync with veza-backend-api/openapi.yaml."
echo "💡 Run: make openapi && cd apps/web && bash scripts/generate-types.sh"
echo "💡 Then stage the updated src/services/generated/ and retry."
echo "💡 Tip: SKIP_TYPES=1 bypasses (not recommended)."
exit 1
}
fi
# Generate TypeScript types from OpenAPI spec before commit
# This ensures types are always up-to-date with the backend API
cd apps/web && bash scripts/generate-types.sh
# Implicit 10.1: Type checking
# Prevent commits with TypeScript errors (warnings are allowed)
(cd apps/web && npm run typecheck 2>&1 | grep -q "error TS") && {
cd apps/web && npm run typecheck 2>&1 | grep -q "error TS" && {
echo "❌ Type checking failed. Please fix TypeScript errors before committing."
echo "💡 Run 'npm run typecheck' to see all errors."
exit 1
} || true
# Implicit 10.2: Linting
# Prevent commits with linting errors (warnings are allowed).
# Pattern matches "(N error" with N>=1 in ESLint's summary line —
# avoids false positive on "(0 errors, K warnings)".
(cd apps/web && npm run lint 2>&1 | grep -qE "\([1-9][0-9]* error") && {
# Prevent commits with linting errors (warnings are allowed)
cd apps/web && npm run lint 2>&1 | grep -q "error" && {
echo "❌ Linting failed. Please fix linting errors before committing."
echo "💡 Tip: Run 'npm run lint:fix' to automatically fix some issues."
exit 1
@ -38,7 +24,7 @@ fi
# Skip if SKIP_TESTS environment variable is set (for quick commits)
# Only runs unit tests (not E2E) to keep it fast
if [ -z "$SKIP_TESTS" ]; then
(cd apps/web && npm test -- --run 2>&1 | grep -q "FAIL") && {
cd apps/web && npm test -- --run 2>&1 | grep -q "FAIL" && {
echo "❌ Tests failed. Please fix failing tests before committing."
echo "💡 Tip: Run 'npm test' to see all test failures."
echo "💡 Tip: Set SKIP_TESTS=1 to skip tests for this commit (not recommended)."

View file

@ -1,35 +0,0 @@
#!/usr/bin/env sh
# ============================================================================
# Veza pre-push hook — CRITICAL E2E SMOKE
# ============================================================================
# Runs only @critical Playwright tests before push (~2-3min).
# SKIP_E2E=1 git push ... # bypass for quick iterations
# ============================================================================
set -e
REPO_ROOT="$(git rev-parse --show-toplevel)"
cd "$REPO_ROOT"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
if [ -n "$SKIP_E2E" ]; then
echo "${YELLOW}▶ SKIP_E2E=1 — skipping critical E2E smoke${NC}"
exit 0
fi
echo "${YELLOW}▶ Running critical E2E smoke tests (Playwright @critical)...${NC}"
echo "${YELLOW} Set SKIP_E2E=1 to bypass (not recommended for shared branches)${NC}"
npm run e2e:critical 2>&1 || {
echo "${RED}✗ Critical E2E tests failed — push blocked${NC}"
echo "${YELLOW} Tip: run 'npm run e2e:critical' locally to debug${NC}"
echo "${YELLOW} Tip: set SKIP_E2E=1 to bypass if you know what you're doing${NC}"
exit 1
}
echo "${GREEN}✓ Critical E2E smoke passed — push allowed${NC}"

View file

@ -1,68 +0,0 @@
/**
* Lighthouse CI Configuration
* v0.14.0 TASK-STAG-003: Validation Lighthouse
*
* Targets:
* Performance >= 85
* Accessibility >= 90
* PWA >= 90 (best-practices proxy when PWA not applicable)
* Best Practices >= 85
* SEO >= 80
*/
module.exports = {
ci: {
collect: {
url: [
`${process.env.STAGING_URL || 'https://staging.veza.app'}/login`,
`${process.env.STAGING_URL || 'https://staging.veza.app'}/register`,
],
numberOfRuns: 3,
settings: {
preset: 'desktop',
// Throttling: simulate cable connection
throttling: {
cpuSlowdownMultiplier: 1,
downloadThroughputKbps: 10240,
uploadThroughputKbps: 5120,
rttMs: 40,
},
// Skip audits that require auth
skipAudits: [
'uses-http2', // Depends on server config
],
},
},
assert: {
assertions: {
// Performance >= 85
'categories:performance': ['error', { minScore: 0.85 }],
// Accessibility >= 90
'categories:accessibility': ['error', { minScore: 0.90 }],
// Best Practices >= 85
'categories:best-practices': ['warn', { minScore: 0.85 }],
// SEO >= 80
'categories:seo': ['warn', { minScore: 0.80 }],
// Core Web Vitals
'first-contentful-paint': ['warn', { maxNumericValue: 1800 }],
'largest-contentful-paint': ['warn', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['warn', { maxNumericValue: 300 }],
// Accessibility specifics (ORIGIN_UI_UX_SYSTEM compliance)
'color-contrast': 'error',
'image-alt': 'error',
'label': 'error',
'button-name': 'error',
'link-name': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'meta-viewport': 'error',
},
},
upload: {
target: 'filesystem',
outputDir: '.lighthouseci',
},
},
};

View file

@ -1,15 +0,0 @@
{
"apps/web/**/*.{ts,tsx}": [
"bash -c 'cd apps/web && npx eslint --max-warnings=0 --fix \"$@\"' --",
"bash -c 'cd apps/web && npx tsc --noEmit -p tsconfig.json'"
],
"apps/web/**/*.{js,jsx,json,css,md}": ["prettier --write"],
"veza-backend-api/**/*.go": [
"bash -c 'cd veza-backend-api && gofmt -l -w \"$@\"' --",
"bash -c 'cd veza-backend-api && go vet ./...'"
],
"veza-stream-server/**/*.rs": [
"bash -c 'cd veza-stream-server && cargo fmt --'"
],
"*.{json,md,yml,yaml}": ["prettier --write"]
}

1
.nvmrc
View file

@ -1 +0,0 @@
20

View file

@ -1,15 +0,0 @@
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 30000,
"wait": 3000,
"chromeLaunchConfig": {
"args": ["--no-sandbox"]
}
},
"urls": [
"http://localhost:5174/login",
"http://localhost:5174/register",
"http://localhost:5174/discover"
]
}

View file

@ -1,11 +0,0 @@
node_modules/
.git/
dist/
storybook-static/
coverage/
*.test.ts
*.test.tsx
*.spec.ts
*_test.go
tests/
loadtests/

View file

@ -1,2 +0,0 @@
10011 IGNORE (Cookie Without Secure Flag - dev only)
10054 IGNORE (Cookie Without SameSite Attribute - dev only)
1 10011 IGNORE (Cookie Without Secure Flag - dev only)
2 10054 IGNORE (Cookie Without SameSite Attribute - dev only)

View file

@ -0,0 +1,370 @@
# Rapport d'état précis des features — Veza
**Date** : 16 février 2026
**Méthode** : Analyse du code source (backend routes, frontend services, migrations DB, tests)
---
## 1. CARTOGRAPHIE GLOBALE — ÉTAT PRÉCIS
### 1.1 Stack
| Couche | Technologie | Version | Fichiers clés |
|--------|-------------|---------|---------------|
| Frontend | React + Vite | 18.2 / 7.1.5 | `apps/web/package.json` |
| Backend | Go + Gin | 1.24 / 1.11 | `veza-backend-api/go.mod` |
| Chat | Rust + Axum | 0.8 | `veza-chat-server/Cargo.toml` |
| Stream | Rust + Axum | 0.8 | `veza-stream-server/Cargo.toml` |
| DB | PostgreSQL | 16 | `docker-compose.prod.yml` |
| Cache | Redis | 7 | idem |
| Queue | RabbitMQ | 3 | idem |
### 1.2 Organisation du repo
- **apps/web** : Frontend React (features/, services/, mocks/)
- **veza-backend-api** : API REST (router principal : `internal/api/router.go`)
- **veza-chat-server** : WebSocket chat
- **veza-stream-server** : Streaming audio
- **veza-common** : Lib Rust partagée
- **packages/** : NPM packages partagés
### 1.3 Point d'entrée API
Le routeur **actif** est `APIRouter` dans `internal/api/router.go`.
Le fichier `api_manager.go` est **exclu de la compilation** (`//go:build ignore`) — tout ce qu'il contient (achievements, leaderboard, GraphQL, gRPC, etc.) est du **code mort**.
---
## 2. ÉTAT PRÉCIS DE CHAQUE FEATURE
### 2.1 Auth (register, login, JWT, refresh)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ Complet | `routes_auth.go` : register, login, refresh, logout, /me, 2FA, OAuth, password reset |
| Frontend | ✅ Complet | `authStore`, `LoginForm`, `TwoFactorVerify`, `ProtectedRoute` |
| DB | ✅ | Tables users, sessions, refresh_tokens, email_verification_tokens |
| Tests | ✅ | `auth_handler_test.go`, `auth_integration_test.go`, `LoginForm.stories` |
| Sécurité | ✅ | JWT iss/aud/exp, token version, bcrypt cost 12, rate limit login |
**Verdict** : **Opérationnel**
---
### 2.2 2FA (TOTP)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `TwoFactorHandler` : setup, verify, disable, status |
| Frontend | ✅ | `TwoFactorSetup.tsx`, `TwoFactorVerify.tsx` |
| DB | ✅ | Colonnes two_factor_enabled, two_factor_secret, backup_codes |
| Tests | ✅ | `two_factor_handler_test.go` |
**Verdict** : **Opérationnel**
---
### 2.3 OAuth (Google, GitHub, Discord)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `OAuthHandler` : providers, initiate, callback |
| Frontend | ✅ | Boutons OAuth, callback handling |
| DB | ✅ | oauth_accounts, users |
**Verdict** : **Opérationnel**
---
### 2.4 Profils utilisateur
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_users.go` : GET/PUT/DELETE /users/:id, settings, avatar, follow, block |
| Frontend | ✅ | `ProfileView`, `ProfilePage`, `useUser` |
| DB | ✅ | users, user_profiles, user_settings |
**Verdict** : **Opérationnel**
---
### 2.5 Upload de tracks (chunked)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_tracks.go` : initiate, chunk, complete, resume, quota |
| Frontend | ✅ | `trackService`, upload flow |
| DB | ✅ | tracks, track_uploads |
| Sécurité | ✅ | RequireContentCreatorRole, ClamAV optionnel |
**Verdict** : **Opérationnel**
---
### 2.6 CRUD Tracks
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | GET/PUT/DELETE tracks, comments, likes, share, versions, play |
| Frontend | ✅ | `trackService`, `LibraryPage`, `TrackDetailPage` |
| DB | ✅ | tracks, track_comments, track_likes |
**Verdict** : **Opérationnel**
---
### 2.7 Playlists (CRUD, collaboration)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_playlists.go` : CRUD, collaborators, tracks |
| Frontend | ✅ | `playlistService`, `PlaylistDetailPage` |
| DB | ✅ | playlists, playlist_collaborators, playlist_tracks |
**Verdict** : **Opérationnel**
---
### 2.8 Chat WebSocket
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_chat.go` : POST /chat/token, GET /chat/stats |
| Chat Server | ✅ | Rust, compile OK |
| Frontend | ✅ | `ChatView`, WebSocket client |
| DB | ✅ | chat_messages (Chat Server) |
**Verdict** : **Opérationnel** (Chat Server doit être démarré)
---
### 2.9 Dashboard
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_core.go:319` : GET /dashboard, `DashboardHandler` |
| Frontend | ✅ | `dashboardService.getDashboardData()` → apiClient.get('/dashboard') |
| MSW | ✅ | Mock dans `handlers-admin.ts` (fallback Storybook) |
**Note** : FEATURE_STATUS.md indiquait "MSW" — **faux**. Le backend expose bien `/api/v1/dashboard`.
**Verdict** : **Opérationnel**
---
### 2.10 Recherche
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `TrackSearchService`, endpoints search |
| Frontend | ✅ | `SearchPage`, `searchService` |
**Verdict** : **Opérationnel**
---
### 2.11 Social (feed, posts, groups, follows, blocks)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_social.go` : feed, posts, groups, like, comments, join/leave |
| Frontend | ✅ | `SocialView`, `useSocialView` |
| DB | ✅ | posts, social_groups, user_follows, user_blocks |
**Verdict** : **Opérationnel**
---
### 2.12 Administration
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_core.go` : admin group, RequireAdmin |
| Frontend | ✅ | `AdminDashboardPage`, `adminService` |
| Audit | ✅ | audit/logs, audit/stats |
**Verdict** : **Opérationnel**
---
### 2.13 Marketplace
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_marketplace.go` : products, cart, orders, licenses |
| Frontend | ✅ | `MarketplacePage`, `Cart`, `PurchasesView` |
| Paiement | ✅ | Hyperswitch intégré |
| DB | ✅ | marketplace_products, orders, licenses |
**Verdict** : **Opérationnel**
---
### 2.14 Webhooks
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_webhooks.go` : CRUD, regenerate-key, test, stats |
| Frontend | ✅ | `webhookService.ts` (apiClient), `WebhooksView` |
| DB | ✅ | webhooks |
**Note** : `webhookApi.ts` supprimé — remplacé par `webhookService.ts` qui appelle l'API directement.
**Verdict** : **Opérationnel**
---
### 2.15 Inventory / Gear
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_gear.go` : GET/POST/PUT/DELETE /inventory/gear |
| Frontend | ✅ | `gearService.ts`, `GearView`, `GearPage` |
| DB | ✅ | Migration 076 : `gear_items` |
| MSW | ✅ | Mock dans `handlers-misc.ts` (Storybook) |
**Note** : FEATURE_STATUS.md indiquait "UI + mocks, pas de backend" — **faux**. Backend complet.
**Verdict** : **Opérationnel**
---
### 2.16 Live Streaming
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_live.go` : GET /live/streams, GET /live/streams/:id, POST (auth) |
| Frontend | ✅ | `liveService.ts`, `LiveView`, `LivePage` |
| DB | ✅ | Migration 077 : `live_streams` |
| MSW | ✅ | Mock dans `handlers-misc.ts` |
**Note** : Le streaming vidéo réel (WebRTC/HLS) est géré par le Stream Server. Les routes backend gèrent les **métadonnées** des streams (titre, description, is_live).
**Verdict** : **Opérationnel** (métadonnées). Stream vidéo dépend du Stream Server.
---
### 2.17 Analytics
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_analytics.go` : tracks plays, top, dashboard |
| Frontend | ✅ | `AnalyticsView`, `useAnalyticsView` |
| DB | ✅ | track_plays, analytics events |
**Verdict** : **Opérationnel**
---
### 2.18 Roles
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `setupRoleRoutes` : assign, revoke |
| Frontend | ✅ | `AssignRoleModal`, `RolesPage` |
| DB | ✅ | roles, user_roles |
**Verdict** : **Opérationnel**
---
### 2.19 Notifications
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ✅ | `routes_core.go` : GET/POST/DELETE /api/v1/notifications, unread-count, read, read-all. Création auto pour follow, like, comment (Phase 2.2) |
| Frontend | ✅ | `NotificationsPage`, `notificationService` |
| DB | ✅ | Table `notifications` (migration 047) |
**Verdict** : **Opérationnel**
---
### 2.20 Gamification (achievements, leaderboard)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ❌ Code mort | `api_manager.go` (build ignore) : handleGetAchievements, handleGetLeaderboard |
| Frontend | ⚠️ Composants | Storybook : AchievementCard, LeaderboardView, XPBar — pas de route /gamification |
| MSW | ? | Handlers gamification possibles dans mocks |
**Verdict** : **Fantôme** — api_manager désactivé, pas de route active
---
### 2.21 Studio (Cloud File Browser)
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ❌ | Aucune route |
| Frontend | ❌ | Dossier `features/studio/` **n'existe pas** (supprimé) |
**Verdict** : **Supprimé**
---
### 2.22 Education
| Aspect | État | Preuve |
|--------|------|--------|
| Backend | ❌ | Aucune route |
| Frontend | ❌ | Dossier `features/education/` **n'existe pas** (supprimé) |
**Verdict** : **Supprimé**
---
## 3. RÉCAPITULATIF
### Features opérationnelles (19)
Auth, 2FA, OAuth, Profils, Upload tracks, CRUD tracks, Playlists, Chat, Dashboard, Recherche, Social, Admin, Marketplace, Webhooks, Gear, Live (métadonnées), Analytics, Roles, Notifications.
### Features partielles (0)
Aucune.
### Features fantômes (1)
Gamification — code dans api_manager (mort), composants Storybook.
### Features supprimées (2)
Studio, Education — dossiers supprimés.
---
## 4. INCOHÉRENCES DOCUMENTATION / CODE
| Document | Affirmation | Réalité |
|----------|-------------|---------|
| FEATURE_STATUS.md | Dashboard : MSW | Backend réel GET /dashboard |
| FEATURE_STATUS.md | Inventory : pas de backend | Backend complet /inventory/gear |
| FEATURE_STATUS.md | Live : contenu minimal | Backend complet /live/streams |
| FEATURE_STATUS.md | Studio : UI seule | Dossier supprimé |
| FEATURE_STATUS.md | Education : MSW | Dossier supprimé |
**Recommandation** : Mettre à jour `docs/FEATURE_STATUS.md` et `apps/web/docs/FEATURE_STATUS.md`.
---
## 5. FICHIERS CRITIQUES PAR FEATURE
| Feature | Backend | Frontend service | Route |
|---------|---------|------------------|-------|
| Auth | routes_auth.go | authStore | /auth/* |
| Tracks | routes_tracks.go | trackService | /tracks/* |
| Playlists | routes_playlists.go | playlistService | /playlists/* |
| Chat | routes_chat.go | - | /chat/* |
| Dashboard | routes_core.go | dashboardService | /dashboard |
| Social | routes_social.go | - | /social/* |
| Marketplace | routes_marketplace.go | - | /marketplace/* |
| Webhooks | routes_webhooks.go | webhookService | /webhooks/* |
| Gear | routes_gear.go | gearService | /inventory/gear |
| Live | routes_live.go | liveService | /live/streams |
| Analytics | routes_analytics.go | - | /analytics/* |
| Roles | routes_users.go | - | /users/:id/roles |
---
*Rapport généré le 16 février 2026*

View file

@ -0,0 +1,737 @@
I now have all the data needed. Let me write the comprehensive audit report.
---
# 🔍 AUDIT COMPLET DU MONOREPO VEZA
**Date** : 16 février 2026
**Auditeur** : Architecte IA senior
**Scope** : Monorepo complet (`veza-backend-api`, `veza-chat-server`, `veza-stream-server`, `veza-common`, `apps/web`)
---
## PARTIE 1 — ÉTAT DE STABILITÉ
---
### 1.1 Santé du code
#### Go Backend (`veza-backend-api/`) ✅
| Critère | Statut | Détail |
|---------|--------|--------|
| Compilation (`go build ./...`) | ✅ Passe | 0 erreur, 0 warning |
| Vet (`go vet ./...`) | ✅ Passe | 0 issue |
| Imports cassés | ✅ Aucun | — |
| `.env.template` | ✅ Documenté | Complet avec validation rules |
| Secrets hardcodés | ✅ Aucun | Tous via env vars, masqués dans logs |
**TODOs/FIXMEs critiques (P1) — 7 items :**
| Fichier | Ligne | Description |
|---------|-------|-------------|
| `internal/core/track/handler.go` | ~340 | `TODO(P2-GO-004)`: `trackUploadService` attend `int64`, reçoit `uuid.UUID` — migration UUID incomplète |
| `internal/core/track/handler.go` | ~355 | `TODO(P2-GO-004)`: même problème, `GetUploadProgress()` incompatible UUID |
| `internal/repositories/playlist_collaborator_repository.go` | ~67 | `FIXME`: modèle `PlaylistCollaborator` doit utiliser UUID |
| `internal/services/playlist_version_service.go` | ~73 | `FIXME`: `PlaylistVersion` ID types à vérifier |
| `internal/services/track_history_service.go` | ~74 | `FIXME`: `TrackHistory` needs UUID migration |
| `internal/services/playlist_service.go` | ~216 | `FIXME`: `PlaylistVersionService` needs UUID update |
| `internal/handlers/auth_handler_test.go` | 225 | `FIXME`: test attend `StatusForbidden` mais l'implémentation permet login non-vérifié |
**TODOs P2 (18 items)** — les plus notables :
| Fichier | Description |
|---------|-------------|
| `internal/services/job_service.go` | Job queue non connectée (5 TODOs BE-SVC-003) — pas d'async processing |
| `internal/database/database.go` | OAuth user lookup non implémenté (3 TODOs) |
| `internal/handlers/oauth_handlers.go` | `frontendURL` fallback hardcodé `http://localhost:5173` |
| `internal/config/middlewares_init.go:75` | Configuration CORS à améliorer |
| `internal/api/admin/service.go` | Admin service partiellement implémenté (3 TODOs) |
#### Rust Chat Server (`veza-chat-server/`) ✅
| Critère | Statut | Détail |
|---------|--------|--------|
| Compilation (`cargo check`) | ✅ Passe | 0 erreur, 0 warning |
| Protobuf | ✅ | Utilise fichiers pré-générés |
| `.env.lab.example` | ⚠️ Minimal | Seul un template lab, pas de `.env.example` standard |
**TODOs (3 items) :**
- `src/read_receipts.rs:230` — TODO: tracking "delivered" non implémenté
- `src/presence.rs:226` — TODO: intégration push notifications (FCM, APNs)
- `src/message_handler.rs:327` — TODO: recherche de salon par nom
#### Rust Stream Server (`veza-stream-server/`) ✅
| Critère | Statut | Détail |
|---------|--------|--------|
| Compilation (`cargo check`) | ✅ Passe | 0 erreur, 0 warning |
| Protobuf | ✅ | Utilise fichiers pré-générés |
| `.env.example` | ✅ Documenté | Variables bien documentées |
| `#![allow(dead_code)]` | ⚠️ | Code mort autorisé dans `lib.rs` |
**Point critique** : le client gRPC vers le backend Go (`src/grpc/mod.rs`) est un **stub**`attempt_send()` fait juste un `sleep`, il n'envoie rien réellement.
#### Rust Common (`veza-common/`) ✅
| Critère | Statut |
|---------|--------|
| Compilation | ✅ Passe |
| TODOs | ✅ Aucun |
#### Frontend React (`apps/web/`) ✅
| Critère | Statut | Détail |
|---------|--------|--------|
| TypeScript (`tsc --noEmit`) | ✅ Passe | 0 erreur |
| Build Vite | ✅ Passe | — |
| `.env.example` | ✅ Documenté | Complet avec feature flags |
**TODOs notables :**
- `src/services/analyticsService.ts:92-97` — endpoints analytics non implémentés côté backend, retournent des valeurs vides
- `src/config/features.ts:50` — HLS endpoints marqués "NOT IMPLEMENTED"
- `src/features/user/components/profile/ProfileSecurity.tsx:12` — "Placeholder for profile security"
---
### 1.2 Points bloquants fonctionnels
| Module | Statut | Détail |
|--------|--------|--------|
| **Auth** | ✅ Fonctionnel | Register → verify email → login → refresh → logout → 2FA TOTP : flow complet. OAuth Google/GitHub opérationnel. Sessions management complet (list/revoke/logout-all). |
| **Profils** | ✅ Fonctionnel | Création, édition, avatar upload, profil public (`/u/:username`), social links, paramètres. Toutes les routes connectées frontend ↔ backend. |
| **Upload & Fichiers** | ⚠️ Partiel | Upload simple ✅, upload chunked ✅, validation MIME/taille ✅, métadonnées extraites ✅. **Manque** : transcoding async (job queue stub), HLS transcoding désactivé (feature flag `false`). |
| **Streaming/Lecteur** | ⚠️ Partiel | Play/pause/seek/next/volume/shuffle/repeat ✅ via `<audio>` HTML5. Waveform visualizer ✅. Queue management ✅. **Manque** : HLS adaptive streaming désactivé, gRPC stream server est un stub, crossfade/gapless non implémentés. |
| **Playlists** | ✅ Fonctionnel | CRUD complet ✅, ajout/retrait tracks ✅, réorganisation ✅, collaboration ✅, share links ✅, export JSON/CSV ✅, duplication ✅. |
| **Chat** | ⚠️ Partiel | WebSocket connection ✅, envoi/réception messages ✅, conversations ✅, typing indicators ✅, reactions ✅. **Manque** : read receipts partiels (TODO), delivered status (TODO), recherche salon par nom (TODO). Communication avec Go backend via HTTP (pas gRPC). |
| **Marketplace** | ✅ Fonctionnel | Création produit ✅, catalogue ✅, panier ✅, wishlist ✅, commandes ✅. Checkout via Hyperswitch (optionnel). Téléchargement post-achat ✅. |
| **Recherche** | ✅ Fonctionnel | Recherche globale tracks/users/playlists ✅, autocomplete ✅. Filtres par type ✅. |
---
### 1.3 Points bloquants techniques
#### Base de données ⚠️
- **42 migrations** bien structurées, idempotentes, avec `IF NOT EXISTS`
- **Migration UUID incomplète** : 6 FIXMEs dans le backend indiquent que certains services (`trackUploadService`, `PlaylistCollaborator`, `PlaylistVersion`, `TrackHistory`) utilisent encore `int64` au lieu de `uuid.UUID`. Cela compile (Go est permissif avec les conversions) mais peut causer des bugs runtime.
- Pas de conflits de migrations détectés
#### API — Routes orphelines ⚠️
**Backend non consommé par le frontend :**
- `POST /api/v1/tracks/initiate` (chunked upload initiate) — frontend utilise directement `/tracks/chunk`
- `POST /api/v1/tracks/complete` (chunked upload complete) — même remarque
- `GET /api/v1/tracks/resume/:uploadId` — pas de UI de reprise d'upload
- `POST /api/v1/tracks/batch/delete` et `POST /api/v1/tracks/batch/update` — pas de UI batch
- `GET /api/v1/tracks/shared/:token` — pas de page de partage par token
- `GET /api/v1/users/me/export` — endpoint existe, pas de bouton export dans l'UI
- `POST /api/v1/audit/cleanup` — pas d'UI admin pour cleanup
**Frontend appelle des endpoints qui n'existent pas côté backend :**
- `POST /api/v1/roles` (création de rôle) — le backend n'a que `GET /roles` et `GET /roles/:id`
- `PUT /api/v1/roles/:id`, `DELETE /api/v1/roles/:id` — idem
- `GET /api/v1/social/feed`, `POST /api/v1/social/posts` — pas de routes social dans le backend (uniquement follow/block)
- `GET /api/v1/social/groups/*` — pas de routes groupes dans le backend
- `GET /api/v1/inventory/gear/*` — pas de routes inventaire dans le backend
- `GET /api/v1/live/streams/*` — pas de routes live dans le backend
- `GET /api/v1/search` — le backend utilise `/tracks/search`, `/users/search`, pas un endpoint unifié `/search`
#### Sécurité ✅
- JWT correctement validé via middleware auth
- CORS configuré (origines spécifiques, pas de wildcard)
- CSRF protection via middleware + tokens
- Security headers complets (HSTS, CSP, X-Frame-Options, X-Content-Type-Options)
- Rate limiting multi-couche (global, par endpoint, par utilisateur)
- SQL injection protection (GORM parameterized queries)
- Secret masking dans les logs
- Aucun secret hardcodé en production (seuls des fallbacks dev dans le code)
#### Services Rust ⚠️
- **Compilation** : ✅ Les deux compilent sans erreur
- **Dépendances Cargo** : ✅ Résolues
- **Communication avec Go** : 🔴 Le stream server utilise un **stub gRPC**`attempt_send()` ne fait qu'un `sleep`. Le chat server communique via HTTP vers le backend Go (fonctionnel mais pas gRPC comme prévu).
#### Docker ✅
- `docker-compose.yml` bien structuré : Postgres 16, Redis 7, RabbitMQ 3, backend-api, Hyperswitch (optionnel)
- Health checks sur tous les services
- Resource limits configurés
- Ports isolés (15xxx/16xxx pour éviter les conflits)
- Fichiers Dockerfile dev et production pour chaque service
#### Frontend — Tests ⚠️
**Tests unitaires (Vitest)** :
- **271/273 fichiers passent** (99.3%)
- **3306/3318 tests passent** (99.6%)
- **2 fichiers échouent** :
1. `src/features/tracks/components/LikeButton.test.tsx` — 11 tests en échec : `aria-label` attend `"Retirer le like"` mais reçoit `"Retirer des favoris"` (problème de label i18n)
2. `src/context/ToastContext.test.tsx` — 1 test en échec : `TypeError: (0, default) is not a function` dans `ToastProvider.tsx:40` (import cassé de `react-hot-toast`)
**Tests E2E (Playwright)** :
- Dernière exécution : **36 tests échoués** (sur un nombre indéterminé — la dernière run a échoué en setup à cause d'un conflit de port 5173)
- Configuration : 4 browsers (Chromium, Firefox, WebKit, Edge), 1 worker, timeout 60s
#### Logs & Observabilité ✅
- Logging structuré : `zap` (Go), `tracing` (Rust)
- Prometheus metrics sur tous les services
- Sentry integration (Go backend, frontend)
- Health checks : `/health`, `/healthz`, `/readyz`, `/api/v1/status`
- Health check détaillé vérifie : DB, Redis, RabbitMQ, S3, chat server, stream server
- Audit logs complets avec recherche
---
### 1.4 Synthèse stabilité
```
PRIORITÉ CRITIQUE (bloque le lancement) :
1. gRPC Stream Server stub — Le stream server ne communique pas réellement avec
le backend Go, la chaîne upload→transcode→stream est cassée.
Fichier: veza-stream-server/src/grpc/mod.rs
Effort: 8h
2. Routes API frontend ↔ backend désalignées — Le frontend appelle des endpoints
inexistants (/social/feed, /social/groups, /inventory/gear, /live/streams, /search).
Ces pages fonctionnent uniquement grâce aux mocks MSW.
Fichiers: apps/web/src/services/socialService.ts, gearService.ts, liveService.ts, searchService.ts
Effort: 16h (créer les routes backend) ou 4h (retirer les pages du routeur)
3. Job Queue non connectée — Les tâches async (transcoding, email, thumbnails) ne
s'exécutent pas en background. Le service existe mais est un shell vide.
Fichier: veza-backend-api/internal/services/job_service.go
Effort: 8h
PRIORITÉ HAUTE (dégrade l'expérience) :
1. Migration UUID incomplète — 6 services utilisent encore int64, risque de bugs
runtime sur upload progress, playlist collaborators, track history.
Fichiers: internal/core/track/handler.go:340, internal/services/playlist_*.go,
internal/repositories/playlist_collaborator_repository.go
Effort: 6h
2. HLS Streaming désactivé — Le lecteur audio ne supporte que le playback direct
(pas d'adaptive bitrate). Feature flag HLS_STREAMING=false.
Fichiers: apps/web/src/config/features.ts, veza-stream-server/
Effort: 12h
3. Tests LikeButton et ToastContext cassés — 12 tests unitaires échouent.
Fichiers: apps/web/src/features/tracks/components/LikeButton.test.tsx,
apps/web/src/context/ToastContext.test.tsx
Effort: 1h
4. Tests E2E non fiables — 36 échecs, configuration port conflict.
Fichier: apps/web/playwright.config.ts (reuseExistingServer: false)
Effort: 4h
PRIORITÉ MOYENNE (acceptable pour un PoC) :
1. Chat read receipts et delivered status — TODOs non implémentés
Fichiers: veza-chat-server/src/read_receipts.rs, src/delivered_status.rs
Effort: 4h
2. OAuth Discord/Spotify non implémentés — Seuls Google et GitHub fonctionnent
Fichiers: veza-backend-api/internal/handlers/oauth_handlers.go
Effort: 4h par provider
3. Admin service partiellement implémenté (3 TODOs)
Fichier: veza-backend-api/internal/api/admin/service.go
Effort: 4h
4. Analytics backend partiellement stub — Certains endpoints retournent des données vides
Fichier: apps/web/src/services/analyticsService.ts:92-97
Effort: 6h
5. Studio et Education supprimés — Features planifiées mais code retiré
Impact: Aucun pour le PoC (Tier 2)
Effort: 0h (décision produit)
```
---
## PARTIE 2 — PROGRESSION VERS L'OBJECTIF FINAL (600 FEATURES)
---
### 2.1 Matrice de couverture par module
> **Note** : Le document TIER 0 mentionne "40 features" mais les ranges listées (`1-10, 31-45, 66-90, 106-135, 151-175, 186-200, 226-250, 351-365, 411-425, 436-450`) contiennent en réalité **190 features**. J'utilise les ranges comme référence.
---
## Module 1 : Auth & Sécurité — 18/30 features (60%)
### Implémentées ✅ (backend + frontend connectés) :
- #1 : Inscription email/password ✅
- #2 : Validation email ✅
- #3 : Connexion email/password ✅
- #4 : OAuth Google ✅
- #5 : OAuth GitHub ✅
- #9 : Logout ✅
- #10 : Logout all devices ✅
- #11 : Reset password par email ✅
- #17 : Blocage après tentatives (rate limiting) ✅
- #19 : 2FA TOTP ✅
- #23 : Session management ✅
- #28 : Rate limiting connexion ✅
### Partiellement implémentées ⚠️ :
- #8 : Remember me ⚠️ — Cookies persistent mais pas de checkbox UI explicite
- #12 : Changement password (authentifié) ⚠️ — Endpoint frontend existe, backend probablement aussi
- #14 : Force du mot de passe ⚠️ — Validation Zod côté frontend, indicateur visuel partiel
- #21 : Codes backup 2FA ⚠️ — Modèle `recovery_code.go` existe, UI incomplète
- #26 : Historique connexions ⚠️ — Via audit logs, pas de page dédiée
- #30 : Détection bruteforce ⚠️ — Via rate limiting, pas de détection spécifique
### Non implémentées ❌ :
- #6 : OAuth Discord ❌
- #7 : OAuth Spotify ❌
- #13 : Historique passwords ❌
- #15 : Politique passwords configurable ❌
- #16 : Expiration password ❌
- #18 : Notification changement password ❌
- #20 : 2FA SMS ❌
- #22 : Passkeys/WebAuthn ❌
- #24 : Notifications connexion inhabituelle ❌
- #25 : Géolocalisation connexions ❌
- #27 : IP whitelisting ❌
- #29 : CAPTCHA ❌
---
## Module 2 : Profils & Utilisateurs — 18/35 features (51%)
### Implémentées ✅ :
- #31 : Avatar upload ✅
- #33 : Username unique ✅
- #34 : Nom complet ✅
- #35 : Bio/description ✅
- #39 : Langue préférée ✅
- #41 : URL profil (/u/username) ✅
- #44 : Liens réseaux sociaux ✅
- #46 : Rôle User ✅
- #47 : Rôle Artist ✅
- #51 : Rôle Modérateur ✅
- #52 : Rôle Admin ✅
- #53 : Permissions granulaires ✅
- #58 : Changement langue UI ✅
- #59 : Thème clair/sombre/auto ✅
- #65 : Supprimer compte (GDPR) ✅
### Partiellement implémentées ⚠️ :
- #32 : Bannière profil ⚠️ — Modèle existe probablement, pas de route dédiée
- #36 : Localisation ⚠️ — Champ probable dans user model
- #42 : Profil public/privé ⚠️ — Paramètres de confidentialité existent
- #56 : Changer email ⚠️ — Endpoint probable
- #57 : Changer username ⚠️ — Via PUT /users/:id
- #60-62 : Notifications on/off ⚠️ — Paramètres existent, implémentation partielle
- #63-64 : Préférences confidentialité/visibilité ⚠️ — Settings partiels
### Non implémentées ❌ :
- #37 : Date de naissance ❌
- #38 : Genre ❌
- #40 : Fuseau horaire ❌
- #43 : Email contact public ❌
- #45 : Badges/achievements ❌
- #48 : Rôle Producer ❌ (distinct d'Artist)
- #49 : Rôle Label ❌
- #50 : Rôle Formateur ❌
- #54 : Système vérification (badge vérifié) ❌
- #55 : KYC ❌
---
## Module 3 : Gestion de Fichiers — 14/40 features (35%)
### Implémentées ✅ :
- #66 : Upload fichier unique ✅
- #67 : Upload multiple (batch) ✅
- #71 : Progress bar upload ✅
- #73 : Validation taille ✅
- #74 : Validation type MIME ✅
- #79 : Extraction métadonnées ✅
- #81-86 : Formats MP3, WAV, FLAC, OGG, AIFF, M4A ✅
- #91-94 : Titre, Artiste, Album, Genre ✅
- #97 : Durée ✅
- #103 : Cover art upload ✅
- #104 : Tags personnalisés ✅
### Partiellement implémentées ⚠️ :
- #68 : Drag & drop ⚠️ — Probable via composant upload
- #72 : Pause/resume upload ⚠️ — Chunked upload existe mais UI incomplète
- #77 : Transcoding auto ⚠️ — Job queue stub, transcoding pipeline Rust existe mais non connecté
- #95 : BPM ⚠️ — Modèle existe, extraction auto incertaine
- #96 : Key musicale ⚠️ — Idem
- #98 : Date de sortie ⚠️ — Champ métadonnée probable
- #105 : Tags suggérés ⚠️ — Autocomplete partiel
### Non implémentées ❌ :
- #69 : Upload par URL ❌
- #70 : Upload depuis cloud (Dropbox/Drive) ❌
- #75 : Scan antivirus ❌ (ClamAV configuré mais `ENABLE_CLAMAV=false`)
- #76 : Compression auto images ❌
- #78 : Thumbnails auto ❌ (job queue stub)
- #80 : Watermarking ❌
- #87-88 : Archives ZIP/RAR ❌
- #89 : Documents PDF ❌
- #90 : Presets VST ❌
- #99-102 : Label, ISRC, Copyright, Lyrics ❌
---
## Module 4 : Streaming Audio — 16/45 features (36%)
### Implémentées ✅ :
- #106 : Play/pause ✅
- #107 : Next track ✅
- #108 : Previous track ✅
- #109 : Seek ✅
- #110 : Volume control ✅
- #111 : Mute/unmute ✅
- #112 : Shuffle ✅
- #113 : Repeat (off/track/playlist) ✅
- #117 : Waveform visualizer ✅
- #122 : Raccourcis clavier ✅ (Media Session API)
- #126 : Queue management ✅
- #127 : Ajouter à la queue ✅
- #128 : Retirer de la queue ✅
- #131 : Vider la queue ✅
- #136 : Créer playlist ✅
- #137 : Éditer playlist ✅
### Partiellement implémentées ⚠️ :
- #120 : Mini-player ⚠️ — Lecteur bottom-bar existe
- #123 : Media Session API ⚠️ — Probable via composant player
- #129 : Réorganiser queue ⚠️ — Store support, UI incertaine
- #132 : Historique écoute ⚠️ — Backend endpoint existe, UI partielle
- #133 : Reprendre où on s'est arrêté ⚠️ — playerStore persiste avec zustand persist
### Non implémentées ❌ :
- #114 : Playback speed ❌
- #115 : Crossfade ❌
- #116 : Gapless playback ❌
- #118 : Spectrogram ❌
- #119 : Bars visualizer ❌
- #121 : Picture-in-picture ❌
- #124 : Chromecast ❌
- #125 : AirPlay ❌
- #130 : Sauvegarder queue comme playlist ❌
- #134 : Queue collaborative ❌
- #135 : Autoplay recommandations ❌
- #138-150 : Playlists CRUD suite (la plupart implémentées — voir Playlists ci-dessus)
> **Correction Playlists** : Features 136-150 sont dans Module 4 mais le CRUD playlist est complet. En réalité : #136-142 ✅, #143 ✅ (collaboration), #144 ⚠️ (cover custom), #145 ✅ (description), #146 ✅ (partage), #147 ✅ (duplication), #148 ❌ (fusion), #149 ✅ (export), #150 ❌ (playlists intelligentes).
---
## Module 5 : Chat & Messagerie — 14/35 features (40%)
### Implémentées ✅ :
- #151 : DM 1-to-1 ✅
- #152 : Salons publics ✅
- #153 : Salons privés ✅
- #154 : Messages de groupe ✅
- #155 : Messages texte ✅
- #157 : Réactions emoji ✅
- #158 : Édition messages ✅
- #159 : Suppression messages ✅
- #170 : Notifications temps réel ✅
- #173 : Badge non lus ✅
- #174 : Typing indicator ✅
### Partiellement implémentées ⚠️ :
- #156 : Emojis ⚠️ — Texte emoji OK, pas de picker dédié
- #160 : Threads/réponses ⚠️ — Infrastructure existe dans le hub Rust
- #175 : Read receipts ⚠️ — Modèle existe, TODO dans le code
### Non implémentées ❌ :
- #161-165 : Mentions, Markdown, images, GIFs, partage tracks ❌
- #166-169 : Recherche historique, filtres, pin, bookmarks ❌
- #171-172 : Push notifications, son personnalisable ❌
- #176-185 : Présence & statuts (en ligne, occupé, custom, AFK, last seen, etc.) ❌
---
## Module 6 : Social & Communauté — 7/40 features (18%)
### Implémentées ✅ :
- #186 : Follow ✅
- #187 : Unfollow ✅
- #188 : Liste followers ✅ (endpoint existe)
- #189 : Liste following ✅
- #190 : Bloquer ✅
- #191 : Signaler ⚠️ (modération backend, pas de bouton frontend dédié)
### Partiellement implémentées ⚠️ :
- #196 : Partage profil ⚠️ — URL `/u/:username` existe
- #198 : Notifications followers ⚠️ — Notifications système existe
### Non implémentées ❌ :
- #192-195, 197, 199-200 : Recommandations, suggestions, collaboration, referral, QR code, close friends, abonnements ❌
- #201-225 : Mur & publications, groupes & communautés ❌ — Le frontend a des composants Social mais ils appellent des endpoints qui **n'existent pas** dans le backend (uniquement MSW mocks)
---
## Module 7 : Marketplace — 16/50 features (32%)
### Implémentées ✅ :
- #226 : Créer produit ✅
- #227 : Éditer produit ✅
- #228 : Supprimer produit ✅
- #229 : Upload fichiers produit ✅
- #233 : Prix fixe ✅
- #236 : Catégories ✅
- #237 : Tags ✅
- #251 : Ajouter au panier ✅
- #252 : Panier multi-produits ✅
- #253 : Wishlist ✅
- #261 : Historique achats ✅
- #262 : Re-téléchargement ✅
- #266 : Dashboard vendeur ✅
### Partiellement implémentées ⚠️ :
- #230 : Preview/démo ⚠️ — Upload existe, player intégré incertain
- #232 : Description rich text ⚠️
- #256 : Checkout (Hyperswitch) ⚠️ — Infrastructure existe, optionnel
### Non implémentées ❌ :
- #231, 234-235, 238-250, 254-260, 263-275 : Images multi, prix variable, gratuit, BPM/Key, formats, licences complètes, paiements avancés, factures, remboursements, revenus temps réel, reviews, promotions, payout ❌
---
## Module 8 : Formation & Éducation — 0/30 features (0%)
**Entièrement non implémenté**. Le répertoire `src/features/education/` a été supprimé. Aucun code backend ne supporte ce module.
---
## Module 9 : Gestion de Matériel — 0/25 features (0%)
⚠️ Le frontend a des composants via MSW mocks (`/api/v1/inventory/gear`), mais **aucun endpoint backend n'existe**. Code frontend-only, non fonctionnel sans mocks.
---
## Module 10 : Cloud & Stockage — 0/20 features (0%)
**Entièrement non implémenté**. Aucune intégration Nextcloud ou backup.
---
## Module 11 : Recherche & Découverte — 6/30 features (20%)
### Implémentées ✅ :
- #351 : Recherche fulltext ✅
- #353 : Recherche tracks ✅
- #357 : Recherche utilisateurs ✅
- #356 : Recherche playlists ✅
- #360 : Autocomplete suggestions ✅
### Partiellement implémentées ⚠️ :
- #352 : Recherche par catégorie ⚠️ — Filtres existent
- #373 : Tri par pertinence ⚠️
### Non implémentées ❌ :
- #354-355, 358-359, 361-380 : Albums, groupes, cours, phonétique, correction ortho, booléen, historique, recherches sauvées, filtres avancés (BPM, key, durée), recommandations ❌
---
## Module 12 : Analytics & Statistiques — 5/30 features (17%)
### Implémentées ✅ :
- #381 : Dashboard analytics ✅
- #383 : Plays par track ✅
### Partiellement implémentées ⚠️ :
- #382 : Statistiques écoute globales ⚠️ — Endpoints partiels, certains retournent des données vides
- #393 : Engagement (likes, comments, shares) ⚠️
- #406 : Utilisateurs actifs (admin) ⚠️ — Admin dashboard partiel
### Non implémentées ❌ :
- #384-392, 394-405, 407-410 : Plays par période, durée moyenne, skip rate, géographie, démographie, devices, sources trafic, peaks, export, revenus, conversions, projections ❌
---
## Module 13 : Administration — 8/25 features (32%)
### Implémentées ✅ :
- #411 : Liste utilisateurs ✅
- #412 : Recherche utilisateurs ✅
- #418 : Changement de rôle ✅
- #419 : Historique actions admin ✅ (audit logs)
- #431 : Paramètres généraux ⚠️ (partiel)
- #433 : Feature flags ✅
### Partiellement implémentées ⚠️ :
- #413 : Filtres avancés ⚠️
- #432 : Limites upload/storage ⚠️ — Configurable via env
### Non implémentées ❌ :
- #414-417, 420-430, 434-435 : Édition profil admin, ban, suspension, reset password, notes internes, modération contenu, copyright, appeal, maintenance mode, annonces ❌
---
## Module 14 : UX/UI — 8/20 features (40%)
### Implémentées ✅ :
- #436 : Thème clair ✅
- #437 : Thème sombre ✅
- #438 : Thème auto ✅
- #446 : Navigation clavier ✅
- #448 : ARIA labels ✅ (partiellement — l'erreur LikeButton montre une incohérence)
- #449 : Focus visible ✅
- #452 : Réduction animations ✅ (prefers-reduced-motion supporté par Framer Motion)
### Partiellement implémentées ⚠️ :
- #450 : Contraste WCAG AA ⚠️ — Design system existe, conformité non auditée
### Non implémentées ❌ :
- #439-445, 447, 451, 453-455 : Contraste élevé, mode compact/confortable, couleurs custom, layouts custom, screen reader complet, tailles police, transcriptions, sous-titres, dyslexie ❌
---
## Modules 15-21 : Fonctionnalités Avancées 🔮
| Module | Features | Implémenté | Statut |
|--------|----------|------------|--------|
| 15. IA & Avancé | 45 | 0 | 🔮 Futur — Aucun code |
| 16. Intégrations | 20 | 0 | 🔮 Futur — Aucun code |
| 17. Apps Natives | 15 | 0 | 🔮 Futur — veza-mobile abandonné |
| 18. Gamification | 15 | 0 | 🔮 Futur — MSW mocks uniquement |
| 19. Notifications | 20 | 5 ⚠️ | ⚠️ Notifications in-app partielles (#551-555) |
| 20. Sécurité Avancée | 15 | 10 ✅ | ✅ Rate limiting, CSRF, XSS, CSP, HSTS, security headers (#571-580), audit logs (#581) |
| 21. Développeurs & API | 15 | 4 ⚠️ | ⚠️ API REST partielle (#586), Swagger (#591), Webhooks (#595), Developer dashboard UI only (#600) |
---
### 2.2 Tableau récapitulatif
| Module | Total | ✅ Done | ⚠️ Partiel | ❌ Missing | 🔮 Future | % Done |
|-------------------------------|-------|---------|------------|-----------|-----------|--------|
| 1. Auth & Sécurité | 30 | 12 | 6 | 12 | 0 | 40% |
| 2. Profils & Utilisateurs | 35 | 15 | 7 | 13 | 0 | 43% |
| 3. Gestion de Fichiers | 40 | 14 | 7 | 19 | 0 | 35% |
| 4. Streaming Audio | 45 | 24 | 5 | 16 | 0 | 53% |
| 5. Chat & Messagerie | 35 | 11 | 3 | 21 | 0 | 31% |
| 6. Social & Communauté | 40 | 5 | 2 | 33 | 0 | 13% |
| 7. Marketplace | 50 | 13 | 3 | 34 | 0 | 26% |
| 8. Formation & Éducation | 30 | 0 | 0 | 0 | 30 | 0% |
| 9. Gestion Matériel | 25 | 0 | 0 | 0 | 25 | 0% |
| 10. Cloud & Stockage | 20 | 0 | 0 | 0 | 20 | 0% |
| 11. Recherche & Découverte | 30 | 5 | 2 | 23 | 0 | 17% |
| 12. Analytics & Statistiques | 30 | 2 | 3 | 25 | 0 | 7% |
| 13. Administration | 25 | 6 | 2 | 17 | 0 | 24% |
| 14. UX/UI | 20 | 7 | 1 | 12 | 0 | 35% |
| 15. Fonctionnalités Avancées | 45 | 0 | 0 | 0 | 45 | 0% |
| 16. Intégrations Externes | 20 | 0 | 0 | 0 | 20 | 0% |
| 17. Applications Natives | 15 | 0 | 0 | 0 | 15 | 0% |
| 18. Gamification | 15 | 0 | 0 | 0 | 15 | 0% |
| 19. Notifications | 20 | 3 | 2 | 5 | 10 | 15% |
| 20. Sécurité Avancée | 15 | 10 | 1 | 0 | 4 | 67% |
| 21. Développeurs & API | 15 | 2 | 2 | 6 | 5 | 13% |
| **TOTAL** | **600** | **129** | **46** | **236** | **189** | **21.5%** |
---
### 2.3 Écart par rapport aux tiers de priorité
#### TIER 0 (V1 Launch — ranges 1-10, 31-45, 66-90, 106-135, 151-175, 186-200, 226-250, 351-365, 411-425, 436-450 = ~190 features)
| Sous-range | Total | ✅ | ⚠️ | ❌ | % |
|------------|-------|-----|------|------|-----|
| Auth 1-10 | 10 | 7 | 1 | 2 | 70% |
| Profils 31-45 | 15 | 10 | 3 | 2 | 67% |
| Fichiers 66-90 | 25 | 10 | 3 | 12 | 40% |
| Streaming 106-135 | 30 | 14 | 4 | 12 | 47% |
| Chat 151-175 | 25 | 11 | 3 | 11 | 44% |
| Social 186-200 | 15 | 5 | 2 | 8 | 33% |
| Marketplace 226-250 | 25 | 10 | 2 | 13 | 40% |
| Recherche 351-365 | 15 | 5 | 2 | 8 | 33% |
| Admin 411-425 | 15 | 4 | 1 | 10 | 27% |
| UX/UI 436-450 | 15 | 7 | 1 | 7 | 47% |
| **TOTAL TIER 0** | **190** | **83** | **22** | **85** | **44%** |
**Estimation effort pour finir TIER 0** : ~85 features manquantes dont beaucoup sont mineures (champs de formulaire, filtres). Estimation réaliste : **200-300h de développement** (6-10 semaines à temps plein).
#### TIER 1 (V2-V5 — ranges 11-30, 46-65, 91-105, 136-150, 176-185, 201-225, 251-275, 276-305, 306-330, 366-410 = ~230 features)
- **Déjà commencées** : ~36 features (2FA #19-21, rôles #46-53, playlists avancées #136-150 partiellement, rate limiting #28)
- Beaucoup de features TIER 1 sont déjà partiellement en place grâce au backend riche
#### TIER 2 (V6-V12 — features 426-435, 451-600 = ~160 features + modules 8-10 = ~75 = ~235 features)
- **Code anticipatoire** : Infrastructure Kubernetes complète (k8s/), monitoring Prometheus/Grafana, load testing scripts, security scanning CI — l'infra est surdimensionnée par rapport au code applicatif.
- Le modèle `live_stream.go` et les composants Live frontend anticipent le livestreaming (#471-480)
- Les modèles `gear.go`, `hardware.go` anticipent l'inventaire (#306-330)
- Les modèles `contest.go`, `royalty.go` anticipent la gamification et les royalties
---
### 2.4 Recommandations stratégiques
#### 1. Les 5 actions les plus impactantes pour la stabilité
1. **Connecter le stream server gRPC au backend Go** (8h) — Sans ça, la chaîne audio est cassée pour le transcoding et les callbacks. Le stream server fonctionne en isolation mais ne communique pas les résultats au backend.
2. **Aligner les routes API social/search/inventory/live** (16h) — Soit créer les endpoints manquants côté Go, soit retirer les pages fantômes du frontend. 4 modules entiers sont en mode "MSW-only".
3. **Connecter la job queue** (8h) — Intégrer `asynq` ou un système similaire pour le transcoding async, les emails, et les thumbnails. Le service est un shell vide.
4. **Finaliser la migration UUID** (6h) — 6 FIXMEs dans le backend risquent des bugs runtime sur les opérations d'upload, collaborateurs de playlist, et historique.
5. **Fixer les 12 tests unitaires cassés et stabiliser les E2E** (5h) — Le LikeButton a un label i18n incorrect, ToastContext a un import cassé, et Playwright a un conflit de port.
#### 2. Choix architecturaux problématiques à l'échelle
- **Stream server gRPC stub** : L'architecture prévoit gRPC pour la communication inter-services, mais les deux implémentations (chat HTTP, stream stub) ne l'utilisent pas vraiment. Cela crée une incohérence architecturale. **Risque** : si le trafic augmente, la communication HTTP entre services ne passera pas à l'échelle aussi bien que gRPC.
- **Double source de vérité pour les services API** : Le frontend a des services à deux endroits (`src/services/*.ts` et `src/features/*/services/*.ts`). Certains endpoints sont appelés depuis les deux. **Risque** : maintenance difficile, bugs de désynchro.
- **Hyperswitch comme payment router** : Choix ambitieux (open-source, multi-provider) mais complexe à opérer. Pour un PoC, Stripe direct serait plus simple. **Risque** : overhead opérationnel important.
- **42 migrations SQL sans outil de migration formel** : Les migrations sont des fichiers SQL bruts. Pas de `migrate` CLI ou de tracking automatique des versions appliquées. **Risque** : conflits et migrations manquées en production.
#### 3. Modules surdéveloppés par rapport à leur priorité
- **Infrastructure Kubernetes** (`k8s/`) : Déploiements, HPA/VPA, monitoring Prometheus/Grafana/Loki, CDN (CloudFront, Cloudflare), certificats Let's Encrypt, network policies, backup cronjobs — tout ça pour un PoC qui n'a pas encore de version stable. **Surdéveloppé** par rapport à l'état du code applicatif.
- **Sécurité avancée (Module 20)** : 67% complété alors que le social (13%), l'analytics (7%), et la recherche (17%) sont très en retard. Le rate limiting multi-couche et les security headers sont parfaits mais disproportionnés pour un PoC.
- **CI/CD** (9 workflows GitHub Actions) : Pipeline complet avec vulnerability scans, SBOM, image signing, smoke tests post-deploy — excellent mais prématuré avant la stabilité fonctionnelle.
#### 4. Modules sous-développés critiques pour le PoC
- **Social & Communauté (13%)** : Pour une plateforme collaborative musicale, le social est le coeur du produit. Les features de feed, posts, groupes n'existent qu'en mocks MSW sans backend.
- **Recherche & Découverte (17%)** : La recherche est basique (fulltext sur tracks/users). Aucun filtre par BPM/key/genre — fonctionnalités critiques pour des musiciens.
- **Analytics (7%)** : Les créateurs ont besoin de voir leurs stats d'écoute. Le dashboard renvoie des données vides sur plusieurs endpoints.
#### 5. Estimation réaliste pour v0.101 stable
| Phase | Contenu | Effort |
|-------|---------|--------|
| Stabilisation technique | gRPC, job queue, UUID migration, tests | 30h |
| Alignement API frontend↔backend | Routes social, search, inventory, live | 20h |
| Core features manquantes | Recherche avancée, analytics basiques, chat complet | 40h |
| Polish & testing | E2E stable, Storybook audit, bug fixes | 20h |
| **TOTAL** | | **110h (~3 semaines à temps plein)** |
---
## SCORE GLOBAL DE MATURITÉ
### 32 / 100
**Détail :**
| Critère | Score | Pondération | Note |
|---------|-------|-------------|------|
| Compilation & santé du code | 95/100 | 15% | Tout compile, peu de TODOs critiques |
| Architecture & structure | 80/100 | 15% | Bien organisé mais incohérences gRPC/HTTP |
| Features TIER 0 | 44/100 | 25% | 44% des features V1 implémentées |
| Tests & qualité | 70/100 | 10% | 99.6% unit pass, E2E instable |
| Intégration inter-services | 30/100 | 15% | gRPC stub, routes orphelines, MSW-only pages |
| Documentation & DevEx | 75/100 | 5% | Bien documenté, env templates complets |
| Sécurité | 85/100 | 10% | Excellente pour un PoC |
| Infrastructure & Ops | 60/100 | 5% | Surdimensionné mais fonctionnel |
**Score pondéré : 32/100**
---
**Synthèse en une phrase** : Veza possède une base technique solide et bien architecturée (compilation propre, 3300+ tests, sécurité exemplaire, infrastructure K8s complète), mais reste à mi-chemin de la stabilité fonctionnelle : le stream server ne communique pas vraiment avec le backend, 4 modules frontend n'existent qu'en mocks, et seulement 44% des features TIER 0 sont implémentées de bout en bout — il faut environ 3 semaines de travail focalisé pour atteindre une v0.101 stable.

View file

@ -1,695 +0,0 @@
# AUDIT_REPORT v2 — monorepo Veza
> **Date** : 2026-04-20
> **Branche** : `main` (HEAD = `89a52944e`, `v1.0.7-rc1`)
> **Auditeur** : Claude Code (Opus 4.7 — mode autonome, /effort max, /plan)
> **Méthode** : 5 agents Explore en parallèle (frontend, backend Go, Rust stream, infra/DevOps, dette transverse) + mesures macro directes + lecture `docs/audit-2026-04/v107-plan.md` + `CHANGELOG.md` v1.0.5 → v1.0.7-rc1.
> **Supersede** : [v1 du 2026-04-14](#annexe-diff-v1-v2) (HEAD `45662aad1`, v1.0.0-mvp-24). Depuis : v1.0.4 → v1.0.5 → v1.0.5.1 → v1.0.6 → v1.0.6.1 → v1.0.6.2 → v1.0.7-rc1. 50+ commits. Le v1 est **obsolète** : son "chemin critique v1.0.5 public-ready" a été réalisé intégralement, mais sa liste de hygiène repo (binaires, screenshots, .git 2.3 GB) est **restée en état**.
> **Ton** : brutal, pas de langue de bois. Citations `fichier:ligne`.
---
## 0. TL;DR — ce que je retiens en 12 lignes
1. **Plomberie produit : solide.** v1.0.5 → v1.0.7-rc1 a fermé tout le "chemin critique" fonctionnel : register/verify réels, player fallback `/stream`, refund reverse-charge Hyperswitch, reconciliation sweep, Stripe Connect reversal worker, ledger-health Prometheus gauges, maintenance mode persisté, chat multi-instance avec alarme loud. 50+ commits, **18 findings v1 résolus**. Détail : [FUNCTIONAL_AUDIT.md](FUNCTIONAL_AUDIT.md).
2. **Hygiène repo : catastrophique.** `.git` = **2.3 GB** (inchangé depuis v1). Binaire `api` de **99 MB** encore à la racine (tracked, ELF). 44 fichiers audio `.mp3/.wav` encore dans `veza-backend-api/uploads/`. 48 screenshots PNG à la racine (`dashboard-*.png`, `login-*.png`, `design-system-*.png`, `forgot-password-*.png`). 36 `.playwright-mcp/*.yml` debris de sessions MCP. `CLAUDE_CONTEXT.txt` = **977 KB** à la racine.
3. **`CLAUDE.md` globalement juste** (v1.0.4, 2026-04-14) mais Vite annoncé "5" → réellement **Vite 7.1.5** (`apps/web/package.json`). Axios "déprécié en dev" → réellement `1.13.5` moderne. `docs/ENV_VARIABLES.md` introuvable alors que CLAUDE.md dit "à maintenir".
4. **Frontend** : 1984 fichiers TS/TSX. **36 features** modulaires. Router propre (27 routes top-level, 54 lazy). `src/types/generated/api.ts` = **6550 lignes, régénéré aujourd'hui** — OpenAPI typegen a démarré. **282 occurrences `any`** (dont `services/api/auth.ts:85-100` triple cast token fallback). **6 `console.log` en prod** (checkbox, switch, slider, AdvancedFilters, Onboarding, useLongRunningOperation). 11 composants UI orphelins (`hover-card/*`, `dropdown-menu/*`, `optimized-image/*`). 3.5 MB de dead reports (`e2e-results.json` 3.4 MB, `lint_comprehensive.json` 793 KB, `ts_errors.log` 29 KB).
5. **Backend Go** : 877 fichiers `.go`, **197K LOC**. 27 fichiers routes, 135 handlers, 226 services, 81 modèles, **160 migrations** (jusqu'à `983_`), 17 workers, 11 jobs. **Transactions manquantes** sur paths critiques (marketplace `service.go:1050+`, subscription). **31 instances `context.Background()` dans handlers** → timeout middleware défait. 3 binaires trackés (`api`, `main`, `veza-api`). **Duplicate `RespondWithAppError`** (`response/response.go:101` + `handlers/error_response.go:12`).
6. **Rust stream server** : Axum 0.8 + Tokio 1.35 + Symphonia. HLS ✅ réel, HTTP Range 206 ✅, WebSocket 1047 LOC ✅, adaptive bitrate 515 LOC ✅. **DASH commenté** (`streaming/protocols/mod.rs:4`). **WebRTC commenté** (`Cargo.toml:62`). **`#![allow(dead_code)]` global** au `lib.rs:5` — camoufle les stubs. 0 `unsafe` (engagement CLAUDE.md tenu). **`proto/chat/chat.proto` orphelin** depuis suppression chat Rust (2026-02-22). `veza-common/src/chat/*` types orphelins.
7. **Chat server Rust** : **confirmé absent** (commit `05d02386d`, 2026-02-22). Zéro référence dans k8s (bon). **`proto/chat/*.proto` reste comme spec historique** — à déplacer en `docs/archive/` ou supprimer.
8. **Desktop Electron** : **confirmé absent**. Jamais implémenté. Fossile des docs anciennes.
9. **Docker** : 6 compose files (dev/prod/staging/test/root/`infra/lab.yml` DEPRECATED Feb 2026). **MinIO pinné `:latest` dans 4 composes** → supply-chain risk. ES 8.11.0 uniquement en dev (orphelin ? backend utilise Postgres FTS). Healthchecks partout mais intervals incohérents (5s→30s). **3 variants Dockerfile par service** (base + .dev + .production) — multi-stage, non-root user `app` (uid 1001), `-w -s` stripped. ⚠️ stream-server Dockerfile.production expose `8082` mais `docker-compose.prod.yml:284` healthcheck attend `3001`**mismatch**.
10. **CI/CD** : 5 workflows actifs (`ci.yml` consolidé + `frontend-ci.yml` + `security-scan.yml` gitleaks + `trivy-fs.yml` + `go-fuzz.yml`). **19 workflows disabled, 1676 LOC mort** (`backend-ci.yml.disabled`, `cd.yml.disabled`, `staging-validation.yml.disabled`, `accessibility.yml.disabled`, etc.). E2E **pas déclenché en CI** alors que Playwright existe. Tests integration skipped (`VEZA_SKIP_INTEGRATION=1`) faute de Docker socket.
11. **Sécurité** : JWT RS256 prod / HS256 dev ✅. OAuth (Google/GitHub/Discord/Spotify) ✅. 2FA TOTP ✅. CORS strict en prod ✅. gitleaks + govulncheck + trivy en CI ✅. **Absents** : CSP header, X-Frame-Options (0 grep hit). **.env committé** (`/veza-backend-api/.env`, `-rw-r--r--`). **TLS certs committés** : `/docker/haproxy/certs/veza.pem`, `/config/ssl/{cert,key,veza}.pem`**rotate + BFG needed**.
12. **Verdict monorepo** : **Moyen-Haute dette sur l'hygiène, Faible dette sur le code applicatif**. Le produit fonctionne, la plomberie monétaire est auditée, la sécurité applicative est solide. Mais les items "cleanup" de l'audit v1 n'ont **pas été traités** : binaires trackés, .git 2.3 GB, screenshots racine, .playwright-mcp debris, CLAUDE_CONTEXT.txt 977 KB, 19 workflows disabled, .env/certs committed. **~1 jour de cleanup brutal reste à faire** avant le tag v1.0.7 final.
---
## 1. État des lieux — mesures macro directes
### 1.1 Taille & fichiers
| Mesure | v1 (14-04) | v2 (20-04) | Delta |
| ------------------------- | ------------ | ------------- | -------------------------------------- |
| `.git` (du -sh) | 2.3 GB | **2.3 GB** | 0 (pas de `git filter-repo` fait) |
| Fichiers trackés | 6425 | **6313** | 112 (quelques cleanups ponctuels) |
| Binaires ELF racine | 3 (api/main/veza-api) | **1 (`api` 99 MB)** | 2 supprimés mais 1 persiste |
| Screenshots racine | 54 | **48** | 6 |
| `.md` total repo | inconnu | **435** (18 active + 417 archive) | — |
| `.playwright-mcp/*.yml` | — | **36 (untracked)** | NEW debris |
| `CLAUDE_CONTEXT.txt` | — | **977 KB** racine | NEW artifact de session |
| `output.txt` racine | — | **27 KB** | NEW |
### 1.2 Ce qui n'existe PAS (contrairement à certaines docs)
| Objet | Status | Preuve |
| ---------------------------------- | :--------------: | ------------------------------------------------------------------------------------------------ |
| `veza-chat-server/` | ❌ absent | `ls /home/senke/git/talas/veza/veza-chat-server` → no such dir. Commit `05d02386d` (2026-02-22). |
| `apps/desktop/` (Electron) | ❌ absent | Jamais implémenté. |
| `backend/` racine | ❌ absent | C'est `veza-backend-api/`. |
| `frontend/` racine | ❌ absent | C'est `apps/web/`. |
| `ORIGIN/` racine | ❌ absent | C'est `veza-docs/ORIGIN/`. |
| `proto/chat/chat.proto` utilisé | ❌ orphelin | 0 import dans `veza-stream-server/src/`. Chat 100% Go depuis v0.502. |
| Runbooks k8s mentionnant chat Rust | ❌ clean (bonne) | Grep `veza-chat-server` dans `k8s/` = 0 hit. |
| **Binaire `api` 99 MB racine** | ⚠️ **présent** | `-rwxr-xr-x 1 senke senke 99515104 Mar 24 15:40 api`. **À supprimer.** |
---
## 2. Architecture & stack — mise à jour exacte
### 2.1 Arborescence réelle
```
veza/ (2.3 GB .git, 6313 fichiers trackés)
├── apps/web/ # React 18.2 + Vite 7.1.5 + TS 5.9.3 + Zustand 4.5 + React Query 5.17
│ └── src/ (1984 fichiers TS/TSX)
│ ├── features/ (36 feature folders)
│ ├── components/ui/ (255 fichiers — design system)
│ ├── services/ (73 fichiers)
│ ├── types/generated/ (api.ts 6550 lignes, régénéré aujourd'hui)
│ └── router/routeConfig.tsx (184 lignes, 27 routes top-level, 54 lazy)
├── veza-backend-api/ # Go 1.25.0 + Gin + GORM + Postgres + Redis + RabbitMQ
│ ├── cmd/api/main.go (orchestration wiring)
│ ├── cmd/{migrate_tool,backup,generate-config-docs,tools/*} (~6 binaires)
│ ├── internal/ (877 fichiers .go, 197K LOC)
│ │ ├── api/ (27 routes_*.go)
│ │ ├── api/handlers/ (3 fichiers DEPRECATED — chat, rbac)
│ │ ├── handlers/ (135 fichiers — source active)
│ │ ├── services/ (226 fichiers, 64K LOC)
│ │ ├── core/*/ (9 services feature-scoped)
│ │ ├── models/ (81 fichiers, 44K LOC)
│ │ ├── migrations/ (160 .sql, jusqu'à 983_)
│ │ ├── workers/ (17) + jobs/ (11)
│ │ ├── middleware/ (~30)
│ │ ├── repositories/ (18 GORM-based)
│ │ └── repository/ (1 ORPHELIN in-memory mock)
│ ├── docs/swagger.{json,yaml} (v1.2.0, 2026-03-03)
│ ├── uploads/ (44 .mp3/.wav TRACKÉS !)
│ └── {api,main,veza-api} (3 binaires ELF trackés dans CLAUDE.md .gitignore mais présents)
├── veza-stream-server/ # Rust 2021 + Axum 0.8 + Tokio 1.35 + Symphonia 0.5 + sqlx 0.8 + tonic 0.11
│ └── src/
│ ├── streaming/ (HLS réel, WebSocket 1047 LOC, adaptive 515 LOC, DASH stub commenté)
│ ├── audio/ (Symphonia + LAME native; opus/webrtc/fdkaac commentés)
│ ├── core/ (StreamManager 10k+ concurrents, sync engine 1920 LOC)
│ ├── auth/ (JWT HMAC-SHA256, revocation Redis+in-mem fallback, 825 LOC)
│ ├── grpc/ (Stream+Auth+Events — generated 21845 LOC auto)
│ ├── transcoding/ (queue job engine 94 LOC — ALPHA)
│ ├── event_bus.rs (RabbitMQ degraded mode, 248 LOC)
│ └── lib.rs:5 #![allow(dead_code)] GLOBAL — camoufle les stubs
├── veza-common/ # Rust types partagés
│ └── src/{chat,ws,files,track,user,playlist,media,api}.rs
│ └── chat.rs, track.rs, user.rs, etc. — ORPHELINS depuis suppression chat Rust
├── packages/design-system/ # Tokens design (unique package workspace)
├── proto/
│ ├── common/auth.proto ✅ utilisé par stream-server + backend
│ ├── stream/stream.proto ✅ utilisé par stream-server
│ └── chat/chat.proto ❌ ORPHELIN (chat en Go depuis v0.502)
├── docs/
│ ├── audit-2026-04/ (NEW : axis-1-correctness.md + v107-plan.md)
│ ├── archive/ (278 fichiers .md historique)
│ └── (API_REFERENCE, ONBOARDING, PROJECT_STATE, FEATURE_STATUS, etc.)
├── veza-docs/ # Docusaurus séparé
│ ├── docs/{current,vision}/
│ └── ORIGIN/ (22 fichiers phase-0 FOSSILE, jamais touchée post-launch)
├── k8s/ # ~30-40 manifests + 5 runbooks disaster-recovery
├── config/ # alertmanager, grafana, haproxy, prometheus, incus, ssl/* (.pem TRACKÉS)
├── infra/ # nginx-rtmp + docker-compose.lab.yml (DEPRECATED)
├── docker/ # haproxy/certs/veza.pem (TRACKÉ, sensible)
├── tests/e2e/ # Playwright — SKIPPED_TESTS.md liste les flakies
├── .github/workflows/ # 5 actifs + 19 .disabled (1676 LOC mort)
├── .husky/ # pre-commit + pre-push + commit-msg (untracked mais fonctionnels)
└── {docker-compose*.yml} # 6 files (dev/prod/staging/test/root/env.example)
```
### 2.2 Stack — versions actuelles
| Composant | Doc (CLAUDE.md) | Réel (code) | Écart ? |
| -------------- | --------------- | ----------------- | ----------------- |
| Go | 1.25 | **1.25.0** (go.mod) | ✅ OK |
| React | 18.2 | 18.2.0 | ✅ OK |
| Vite | **5** | **7.1.5** | ❌ CLAUDE.md obsolète |
| TypeScript | 5.9.3 | 5.9.3 | ✅ OK |
| Zustand | — | 4.5.0 | N/A |
| React Query | 5 | 5.17.0 | ✅ OK |
| Tailwind | — | **4.0.0** | ✅ récent |
| date-fns | 4 | 4.1.0 | ✅ OK |
| Axios | non mentionné | 1.13.5 | ✅ moderne |
| jwt-go | v5 | v5.3.0 | ✅ OK |
| gorm | — | v1.30.0 | ✅ OK |
| gin | — | v1.11.0 | ✅ OK |
| redis-go | — | v9.16.0 | ✅ OK |
| Rust edition | 2021 | 2021 | ✅ OK |
| Axum | 0.8 | 0.8 | ✅ OK |
| Tokio | 1.35 | 1.35 | ✅ OK |
| Symphonia | 0.5 | 0.5 | ✅ OK |
| sqlx | 0.8 | 0.8 | ✅ OK |
| tonic | — | 0.11 | ✅ récent |
| Postgres | 16 | 16-alpine (pinned)| ✅ OK |
| Redis | 7 | 7-alpine (pinned) | ✅ OK |
| ES | 8.11.0 | 8.11.0 (dev only) | ⚠️ orphelin prod |
| RabbitMQ | 3 | 3 (pinned) | ✅ OK |
| ClamAV | 1.4 | 1.4 (pinned) | ✅ OK |
| MinIO | — | **`:latest`** (4×)| ❌ supply-chain |
| Hyperswitch | 2026.03.11.0 | 2026.03.11.0 | ✅ OK |
**À corriger dans CLAUDE.md v1.0.5** : Vite 5 → Vite 7.1.5. Ajouter ligne MinIO.
---
## 3. Frontend (`apps/web/`)
### 3.1 Architecture & routes
- **36 feature folders** (`src/features/`) — les plus gros : `playlists/` (182), `tracks/` (181), `auth/` (100), `player/` (94), `chat/` (67).
- **Router** (`src/router/routeConfig.tsx:1-184`) — 27 routes top-level, **54 composants lazy**. **Zéro route "Coming Soon"/placeholder**. Tous les paths mènent à un composant réel.
- **OpenAPI typegen enclenché** : `src/types/generated/api.ts` = **6550 lignes, régénéré 2026-04-19 00:57:21**. La migration "kill hand-written services" prévue post-v1.0.4 a démarré. Script `apps/web/scripts/generate-types.sh` wiré en pre-commit.
### 3.2 Composants & design system
- `src/components/ui/` : **255 fichiers**. Untracked : `testids.ts` (NEW, probablement wiring E2E).
- **Composants orphelins identifiés** (0-1 imports — candidates suppression) :
- `components/ui/optimized-image/OptimizedImageSkeleton.tsx` (0)
- `components/ui/optimized-image/ResponsiveImage.tsx` (0)
- `components/ui/hover-card/*` (3 fichiers, 0 imports — arbre mort)
- `components/ui/dropdown-menu/*` (7 fichiers, 0-1 imports — probablement remplacé par Radix)
- Total : **~11 fichiers orphelins dans le DS**.
### 3.3 State & services
- **Zustand** : 5 stores principaux (`authStore`, `chatStore`, `playerStore`, `queueSessionStore`, `cartStore`) — tous utilisés.
- **React Query** : **seulement 9 fichiers** utilisent `useQuery/useMutation`. `queryKey` ad-hoc (hardcoded, dynamic, constants mélangés). **Pas de factory centralisée** → cache invalidation fragile.
- **Services** (73 fichiers) :
- Top 4 monolithes : `services/api/auth.ts:553` (token+login+register+2FA), `services/adminService.ts:474` (7+ endpoints), `services/analyticsService.ts:472`, `services/marketplaceService.ts:351`.
- **Anti-pattern critique** : `services/api/auth.ts:85-100` fait 3 fallback `const rd = response.data as any` pour parser les tokens. **Pas de validation Zod.**
### 3.4 Tests
- **286 fichiers `.test.ts(x)`** (Vitest).
- **1 test skipped** : `features/auth/pages/ResetPasswordPage.test.tsx` (async timing).
- **E2E** (racine `tests/e2e/`) : Playwright présent, **SKIPPED_TESTS.md documente les flakies** (v107-e2e-04/05/06/08/09 à vérifier en staging).
- Tests E2E **PAS déclenchés en CI** (Playwright absent de `.github/workflows/ci.yml`).
### 3.5 Dette frontend
| Dette | Count | Sévérité |
| ---------------------------------- | :---: | :------: |
| `TODO/FIXME/HACK` | 1 | ✅ top |
| `console.log` en production | 6 fichiers (checkbox, switch, slider, AdvancedFilters, Onboarding, useLongRunningOperation) | 🔴 |
| `any` types | 282 | 🔴 |
| `@ts-ignore` / `@ts-expect-error` | 6 fichiers | 🟡 |
| Fichiers >500 LOC (non-gen) | ~8 | 🟡 |
| Composants V2/V3/_old/_new | 0 | ✅ |
| `src/types/v2-v3-types.ts` | présent (mentionné CLAUDE.md) | 🟡 |
### 3.6 Artefacts morts à la racine de `apps/web/`
| Fichier | Taille | Date (mtime) | Status |
| ---------------------------- | ------ | ------------ | ----------------- |
| `e2e-results.json` | 3.4 MB | Mar 15 | 🔴 obsolète |
| `lint_comprehensive.json` | 793 KB | Jan 7 | 🔴 obsolète |
| `e2e-results.json` (2) | 241 KB | Jan 7 | 🔴 doublon |
| `ts_errors.log` | 29 KB | Dec 12 | 🔴 2+ mois stale |
| `storybook-roadmap.json` | 8.5 KB | Mar 6 | 🟡 |
| `AUDIT_ISSUES.json` | 19 KB | Dec 17 | 🔴 |
| `audit.log`, `debug-storybook.log` | 8.5 KB | Feb/Mar | 🟡 |
**~3.5 MB de reports morts** au bord du frontend. CLAUDE.md §règles 11 interdit ces fichiers en git (ils sont ignorés via `.gitignore` mais traînent en untracked).
---
## 4. Backend Go (`veza-backend-api/`)
### 4.1 Structure
- **877 fichiers .go** dans `internal/`
- **27 fichiers `routes_*.go`** (1 est un test)
- **135 handlers actifs** dans `internal/handlers/`
- **3 fichiers dans `internal/api/handlers/`** — confirmés DEPRECATED (chat + RBAC, à purger après confirmation aucun import)
- **226 services** (`internal/services/`) + **9 core services** (`internal/core/*/service.go`)
- **81 modèles** (`internal/models/`, 44K LOC) — pattern GORM + soft-delete
- **160 migrations SQL** (jusqu'à `983_hyperswitch_webhook_log.sql`)
- **17 workers** + **11 jobs**
- **~30 middlewares**
### 4.2 Routes & handlers
Handlers complets par domaine, **zéro endpoint retournant 501 ou vide**. Zéro double wiring.
Top routes par taille : `routes_core.go:512` (20+ routes), `routes_auth.go:245` (14+ routes, 2FA/OAuth inclus), `routes_tracks.go:240` (18+), `routes_users.go:296` (17+), `routes_marketplace.go:174` (15+), `routes_webhooks.go:205` (5+ ; raw payload audit).
### 4.3 Auth
| Aspect | Status | Preuve |
| -------------------- | :----: | ---------------------------------------------------------------------------------------------------- |
| JWT RS256 prod | ✅ | `services/jwt_service.go:17-81`, keys depuis env. |
| HS256 dev fallback | ✅ | Idem, 32+ char secret exigé. |
| Refresh 7j / Access 5min | ✅ | Configurés. |
| 2FA TOTP + backup codes | ✅ | `handlers/two_factor_handler.go:171` (actif). `api/handlers/` vide de 2FA — deprecated purgé. |
| OAuth 4 providers | ✅ | `routes_auth.go:122-176` (Google, GitHub, Discord, Spotify). State encrypté via CryptoService. |
| Rate limiting multi-couche | ✅ + 🟡 | DDoS global 1000 req/s ✅, endpoint-specific ✅, API key ✅, **`UserRateLimiter` configuré mais pas wiré aux routes**. |
| CSRF | ✅ | Middleware actif (e2e confirmé `tests/e2e/45-playlists-deep.spec.ts`). Disabled dev/staging (`router.go:133`). |
| Security headers | 🟡 | SecurityHeaders middleware présent (`router.go:204`). **CSP / X-Frame-Options pas vus en grep**. À vérifier. |
### 4.4 Modèles, DB, transactions
- Migrations auto-appliquées au démarrage (`database.go:234-256`). Boot fail si erreur SQL.
- Repositories : 18 GORM-direct, pattern inline (pas d'interface). **Plus** `internal/repository/` (1 fichier in-memory mock UserRepository) **ORPHELIN** — à supprimer.
- **Transactions insuffisantes**`db.Transaction()` usage = **8×**, `tx.Create/Save/Delete` manuel = **37×**. Chemins critiques (marketplace `core/marketplace/service.go:1050+`, subscription) ne sont **pas dans des transactions explicites**. Risque data corruption si une étape échoue au milieu.
### 4.5 Services & context
- Architecture dual-layer `core/` + `services/` **incohérente** : certaines features ont `core/service.go`, d'autres `services/*.go`, sans règle claire. Ex. track publication en `core/track/` mais search indexing en `services/track_search_service.go`, les deux appelés depuis un même handler.
- Context propagation : 558 usages propres dans services, **mais 31 `context.Background()` dans `handlers/`** → défait le timeout middleware. Fix grep+sed 1 jour.
- **Pas de `services_init.go`** : services instantiés inline dans `routes_*.go`. Re-créés par request-group. Non-singletons.
### 4.6 Workers & jobs
- **Actifs lancés par `cmd/api/main.go`** : JobWorker, TransferRetry, StripeReversal, Reconciliation, CloudBackup, GearWarranty, NotifDigest, HardDelete, OrphanTracksCleanup, LedgerHealthSampler.
- **Jobs définis mais jamais schedulés** : `SchedulePasswordResetCleanupJob`, `CleanupExpiredSessions`, `CleanupVerificationTokens`, `CleanupHyperswitchWebhookLog` — ~4 cleanup jobs **dead code**. Soit les brancher soit les supprimer.
### 4.7 Tests
- **364 fichiers `*_test.go`**. `coverage_v1.out` (Mar 3) indique ~60-70%.
- Integration tests skippables via config — mais **pas de variable `VEZA_SKIP_INTEGRATION` trouvée en grep** (CLAUDE.md la mentionne — à vérifier si elle existe réellement ou si c'est un fossile doc).
- E2E Playwright n'entre jamais en CI.
### 4.8 Validation & errors
- `internal/validators/` — wrapper `go-playground/validator/v10`
- `internal/errors/``AppError{Code,Message,Err,Details,Context}`
- **PROBLÈME** : `RespondWithAppError` défini **2 fois** (`response/response.go:101` + `handlers/error_response.go:12`). Duplication à consolider.
- Wrapped errors : 349 usages `errors.Is/As/Unwrap` — bon pattern.
### 4.9 Config
- **99 env vars lues** dans `config/config.go` (1087 LOC)
- **`Config.Validate()`** :
- ✅ Refuse prod si `HYPERSWITCH_ENABLED=false` (`config.go:908-910`, fail-closed).
- ✅ Refuse prod sans DATABASE_URL, JWT keys, CORS origins.
- ❌ **Pas de check `APP_ENV ∈ {dev,staging,prod}`** — silencieusement default dev.
- ❌ **Pas de check `UPLOAD_DIR` exists** — boot success même si dir manquant.
- **`.env.template` 190 lignes** vs 263 `os.Getenv` appels code → drift potentiel (~70 vars documentées vs 99 utilisées).
### 4.10 Dette backend — récap
| Dette | Sévérité | Effort | Preuve |
| ------------------------------------------- | :-------: | :----: | ------------------------------------------------------------- |
| Transactions manquantes marketplace/subs | 🔴 | M (3j) | `core/marketplace/service.go:1050+` |
| 31× `context.Background()` dans handlers | 🔴 | S (1j) | Grep handlers |
| Binaires racine `api` (99MB) + 44 .mp3 | 🔴 | XS (1h)| `git rm --cached` + BFG |
| `RespondWithAppError` dupliqué | 🟡 | S (1j) | `response/response.go:101` + `handlers/error_response.go:12` |
| `internal/repository/` orphelin | 🟡 | XS | Delete dir |
| 4 cleanup jobs jamais schedulés | 🟡 | S | Brancher ou supprimer |
| `UserRateLimiter` configuré non wiré | 🟡 | S | Wire en middleware chain |
| Écart `.env.template` vs code (29 vars) | 🟠 | S | Sync |
| Services re-instantiés par request-group | 🟠 | M | `services_init.go` + singleton pattern |
| Architecture core/+services/ incohérente | 🟠 | L | Document la règle OU unifier |
---
## 5. Rust stream server (`veza-stream-server/`)
### 5.1 Modules
Production-ready : `streaming/` (HLS réel, Range 206, WS 1047 LOC, adaptive 515 LOC), `audio/` (Symphonia native, compression 708 LOC, effects SIMD), `core/` (StreamManager 10k+ concurrents, sync engine NTP-like 1920 LOC), `auth/` (JWT HMAC-SHA256 + revocation Redis-or-in-mem 825 LOC), `cache/` (LRU audio), `event_bus.rs` (RabbitMQ degraded mode).
Alpha / partiel : `transcoding/engine.rs` (94 LOC, job queue priority-based mais **zéro test d'intégration, zéro tracking live**), `grpc/` (461 LOC business + 21845 LOC généré).
**Stub / absent** :
- `streaming/protocols/mod.rs:4``// pub mod dash;` **commenté**.
- `Cargo.toml:62``// webrtc = "0.7"` **commenté** (deps natives manquantes).
### 5.2 Audio codecs
Symphonia couvre MP3, FLAC, Vorbis, AAC **natifs**. LAME MP3 via `minimp3 0.5` (natif). **Commentés** : `opus 0.3` (cmake), `lame 0.1`, `fdkaac 0.7` (non sur crates.io).
### 5.3 gRPC & protos
`StreamService`, `AuthService`, `EventsService` (3 services). Utilise `proto/common/auth.proto` + `proto/stream/stream.proto`. **`proto/chat/chat.proto` = 0 import** → orphelin depuis suppression chat Rust.
### 5.4 Dette Rust
| Dette | Sévérité | Preuve |
| ----------------------------------------------- | :------: | ---------------------------------------------------------------- |
| `#![allow(dead_code)]` global dans `lib.rs:5` | 🔴 | Masque tous les stubs. Devrait être granulaire par module. |
| 10× `unwrap()` sur broadcast channels | 🔴 | `core/sync.rs:1037-1110`. Panic si receiver drop. `.expect()` + contexte. |
| `proto/chat/chat.proto` orphelin | 🟡 | À archiver/supprimer. |
| `veza-common` chat types orphelins | 🟡 | ~60 LOC dead. Audit grep `use veza_common::chat` → 0 hit. |
| `transcoding/` zéro tests intégration | 🟡 | `engine.rs:36-62`. |
| 26× `println!/dbg!` | 🟡 | Devrait utiliser `tracing::`. |
| Deps inutilisées (`daemonize`, `notify`) | 🟠 | `Cargo.toml:139, 116`. |
**0 `unsafe`** ✅ (engagement CLAUDE.md tenu).
---
## 6. Infrastructure & DevOps
### 6.1 Docker Compose (6 fichiers)
| Fichier | Rôle | État |
| ---------------------------- | --------------------------------- | ------------------------------------------ |
| `docker-compose.yml` | Dev full-stack avec profiles | ✅ Actif |
| `docker-compose.dev.yml` | Infra-only (209 LOC) | ✅ Actif (MailHog + ES 8.11.0 ici uniquement)|
| `docker-compose.prod.yml` | Blue-green, HAProxy, Alertmanager (464 LOC) | ✅ Actif (Mar 12) |
| `docker-compose.staging.yml` | Caddy (202 LOC) | ✅ Actif (Mar 2) |
| `docker-compose.test.yml` | tmpfs CI (64 LOC) | ✅ Actif |
| `infra/docker-compose.lab.yml` | DEPRECATED Feb 2026 | 🔴 À supprimer |
**Pinning** :
- ✅ Postgres 16-alpine, Redis 7-alpine, RabbitMQ 3, ClamAV 1.4, Hyperswitch 2026.03.11.0.
- ❌ **MinIO `:latest`** dans 4 composes → supply-chain attack vector.
**Services orphelins en dev-only** :
- ES 8.11.0 uniquement `docker-compose.dev.yml:171-204` (34 LOC) — **le backend utilise Postgres FTS, pas ES** (`fulltext_search_service.go`). ES ne sert qu'au hard-delete worker (GDPR cleanup), optionnel. À documenter ou retirer.
### 6.2 Dockerfiles
- Backend : `Dockerfile` + `Dockerfile.production` (Go 1.24-alpine, multi-stage, non-root uid 1001, `-w -s`). ⚠️ **CLAUDE.md dit Go 1.25, Dockerfile sur 1.24** — bumper.
- Stream : `Dockerfile` + `Dockerfile.production` (rust:1.84-alpine). ⚠️ **Mismatch port** : Dockerfile.production expose `8082` mais `docker-compose.prod.yml:284` healthcheck attend `3001`**le Dockerfile n'est pas utilisé en prod** (sans doute l'image vient d'ailleurs).
- Web : `Dockerfile` + `Dockerfile.dev` + `Dockerfile.production` (node:20-alpine → nginx:1.27-alpine).
### 6.3 CI/CD
**Workflows actifs (5)** :
1. `ci.yml` (consolidé, ~15min) — backend Go (test, lint, vet, govulncheck), frontend (lint, tsc, build, vitest), rust (build, test, clippy, audit).
2. `frontend-ci.yml` (55 LOC) — path-triggered React-only, bundle-size gate, npm audit.
3. `security-scan.yml` — gitleaks v8.21.2 secret scan.
4. `trivy-fs.yml` — Trivy filesystem scan (HIGH+CRITICAL exit=1).
5. `go-fuzz.yml` — Nightly fuzz 60s, corpus upload.
**Workflows disabled (19 fichiers, 1676 LOC mort)** :
`backend-ci.yml.disabled`, `cd.yml.disabled`, `staging-validation.yml.disabled`, `accessibility.yml.disabled`, `chromatic.yml.disabled`, `visual-regression.yml.disabled`, `storybook-audit.yml.disabled`, `contract-testing.yml.disabled`, `zap-dast.yml.disabled`, `container-scan.yml.disabled`, `semgrep.yml.disabled`, `sast.yml.disabled`, `mutation-testing.yml.disabled`, `rust-mutation.yml.disabled`, `load-test-nightly.yml.disabled`, `flaky-report.yml.disabled`, `openapi-lint.yml.disabled`, `commitlint.yml.disabled`, `performance.yml.disabled`.
**→ 1676 lignes de workflow mort. Soit réactiver ce qui fait sens (SAST, DAST, openapi-lint), soit archiver dans `docs/archive/workflows/` pour ne pas polluer `.github/workflows/`.**
**Gaps CI** :
- E2E Playwright pas déclenché (pourtant `tests/e2e/` existe, `SKIPPED_TESTS.md` documente les flakies).
- Integration tests Go skipped (`VEZA_SKIP_INTEGRATION=1` faute de Docker socket sur runner).
### 6.4 K8s
- ~30-40 manifests, structure propre (`autoscaling/`, `backends/`, `backups/`, `cdn/`, `disaster-recovery/`, `environments/{prod,staging,dev}`, `secrets/`).
- **5 runbooks** : cluster-failover, database-failover, data-restore, rollback-procedure, security-incident.
- ✅ **Zéro référence à `veza-chat-server`** dans `k8s/` (grep clean — l'audit v1 disait qu'il y avait 7+ runbooks outdated ; **corrigé**).
### 6.5 Secrets & sécurité
| Item | État | Action |
| --------------------------------------------- | :------: | -------------------------------------------------------------------- |
| `/docker/haproxy/certs/veza.pem` | 🔴 TRACKED | BFG + rotate cert + move to K8s Secret |
| `/config/ssl/{cert,key,veza}.pem` | 🔴 TRACKED | Idem |
| `veza-backend-api/.env` | 🔴 TRACKED | `git rm --cached`, rotate JWT/DB secrets dev, relire `.gitignore` |
| `veza-backend-api/.env.production.example` | 🟢 OK | Template |
| Hardcoded secrets en code (`sk_live_`, `AKIA`)| ✅ absent | Grep clean |
| gitleaks en CI | ✅ | `security-scan.yml` |
| govulncheck | ✅ | `ci.yml` |
| CSP header | 🟡 | Grep 0 hit. **À implémenter.** |
| X-Frame-Options | 🟡 | Idem |
### 6.6 Observability
- Prometheus : **5 gauges ledger-health** déployées en v1.0.7 (`ledger_metrics.go`), **+ counter/histogram reconciler**. Alertmanager `config/alertmanager/ledger.yml` avec 3 règles (VezaOrphanRefundRows, VezaStuckOrdersPending, VezaReconcilerStale). Grafana dashboard `config/grafana/dashboards/ledger-health.json`.
- Logs : JSON structuré confirmé (`level`, `time`, `msg`, `request_id`, `user_id`).
- **Gap** : `/metrics` endpoint global backend pas vu (à confirmer — il existe probablement via middleware Sentry/Prometheus, mais pas en grep direct).
- Sentry : optionnel via env (`SENTRY_DSN`, `SENTRY_SAMPLE_RATE_*`).
---
## 7. Documentation
### 7.1 Racine du repo
| Fichier | Taille | Date | Verdict |
| ------------------------------- | ------ | ---------- | ---------------------------------------------------------------------- |
| `CLAUDE.md` | 22 KB | 2026-04-14 | ✅ Autorité. Petite dérive : Vite 5 → 7.1.5 à corriger. |
| `CHANGELOG.md` | 87 KB | 2026-04-19 | ✅ À jour (v0.201 → v1.0.7-rc1). |
| `README.md` | 2.8 KB | — | ✅ Minimal OK. |
| `CONTRIBUTING.md` | 2.7 KB | 2026-02-27 | ✅ OK. |
| `VERSION` | — | — | `1.0.7-rc1` ✅ aligné. |
| `VEZA_VERSIONS_ROADMAP.md` | 69 KB | — | ⚠️ Historique v0.9xx, peu utile post-launch. Archive. |
| `RELEASE_NOTES_V1.md` | 4.7 KB | — | ✅ OK. |
| `AUDIT_REPORT.md` | 57 KB | 2026-04-14 | 🔄 **Ce fichier — v2 remplace v1**. |
| `FUNCTIONAL_AUDIT.md` | 43 KB | 2026-04-19 | ✅ v2 à jour. |
| `UI_CONTEXT_SUMMARY.md` | 6 KB | — | 🟠 Session artifact, devrait être archivé selon CLAUDE.md §12. |
| `CLAUDE_CONTEXT.txt` | 977 KB | 2026-04-18 | 🔴 ÉNORME session dump. Archive ou supprime. |
| `output.txt` | 27 KB | 2026-04-18 | 🔴 Debris. |
| `generate_page_fix_prompts.sh` | 42 KB | Mar 26 | 🟡 Script généré, probablement obsolète. |
| `build-archive.log` | 974 B | Mar 25 | 🟡 Log. |
**48 screenshots PNG racine** (`dashboard-*.png`, `login-*.png`, `design-system-*.png`, `forgot-password-*.png`) — **à déplacer dans `docs/screenshots/` ou supprimer**.
### 7.2 `docs/` (18 actifs + 417 archive = 435 .md)
**Actifs** :
- `docs/API_REFERENCE.md` (1022 LOC) — **manuel**, pas de typegen. Écart flag vs routes Go. Migration vers OpenAPI typegen backend = priorité.
- `docs/ONBOARDING.md`, `docs/PROJECT_STATE.md`, `docs/FEATURE_STATUS.md` — à cross-checker avec code v1.0.7 (non fait ici).
- `docs/ENV_VARIABLES.md`**introuvable en `ls docs/`** alors que CLAUDE.md dit "à maintenir". Soit créé soit manque.
- `docs/audit-2026-04/`**NOUVEAU, très utile** : `axis-1-correctness.md` + `v107-plan.md` — trace des findings et du plan v1.0.7.
- `docs/SECURITY_SCAN_RC1.md` / `docs/ASVS_CHECKLIST_v0.12.6.md` / `docs/PENTEST_REPORT_VEZA_v0.12.6.md`**refs v0.12.6, obsolètes** pour v1.0.7. Refaire ou archiver.
**Archive** (`docs/archive/` = 278 fichiers) : historique session 2026. Taille totale importante. Ne pose pas de problème immédiat.
### 7.3 `veza-docs/` (Docusaurus séparé)
- `veza-docs/docs/{current,vision}/` — doc cible.
- `veza-docs/ORIGIN/` (22 fichiers, ~70K lignes) — **phase-0, jamais touchée depuis launch**. Qualifiée "FOSSIL" par agent. Archive ou zip.
---
## 8. Dette technique transverse — catalogue
### 8.1 TODOs / FIXMEs (11 hits)
1. `tests/e2e/22-performance.spec.ts:8` — "Either add data-testid containers or rewrite test to use API mocking" (3 occurrences).
2. `tests/e2e/04-tracks.spec.ts` — "Corriger le bug dans FeedPage.tsx" (ouvert, P1).
3. `apps/web/src/features/auth/pages/ResetPasswordPage.test.tsx` — async timing flaky.
4. `veza-backend-api/internal/core/marketplace/service.go:1450` — "TODO v1.0.7: Stripe Connect reverse-transfer API" (**effectivement déjà landed en v1.0.7 item A+B** — TODO à supprimer).
5. `veza-backend-api/internal/core/subscription/service.go` — "TODO(v1.0.7-item-G): subscription pending_payment state" (in-flight, parked).
**Aucun TODO daté >6 mois.** Discipline correcte.
### 8.2 Code mort / orphelin
| Item | Action |
| ------------------------------------------------ | ------------------------------------------------ |
| `veza-backend-api/internal/api/handlers/` (3 fichiers) | Confirmer 0 import puis `git rm -r` |
| `veza-backend-api/internal/repository/` (in-mem mock) | `git rm -r` |
| `apps/web/src/components/ui/hover-card/*` (3) | Delete si confirmé 0 import |
| `apps/web/src/components/ui/dropdown-menu/*` (7) | Audit imports, delete si Radix les remplace |
| `apps/web/src/components/ui/optimized-image/{OptimizedImageSkeleton,ResponsiveImage}.tsx` | Delete |
| `apps/web/src/types/v2-v3-types.ts` | Auditer appelants, renommer ou delete |
| `proto/chat/chat.proto` | Archiver `docs/archive/proto-chat/` ou delete |
| `veza-common/src/chat.rs` + autres types chat | Audit `use veza_common::chat`, delete si 0 hit |
| 19 workflows `.disabled` | Archiver `docs/archive/workflows/` ou delete |
| 4 cleanup jobs jamais schedulés (pw-reset, sessions, verif, hyperswitch-log) | Brancher ou delete |
### 8.3 Binaires / artefacts trackés
| Item | Taille | Action |
| --------------------------------------------------- | ------ | ------------------------------------------------- |
| `api` (racine, ELF) | 99 MB | `git rm --cached api` + `.gitignore` |
| `veza-backend-api/{main,veza-api,seed,server}` | ~50 MB chacun | Idem (sont dans `.gitignore` mais encore tracked?) |
| `veza-backend-api/uploads/*.{mp3,wav}` (44 fichiers)| 12 MB | `git rm -r --cached uploads/` + move to git-lfs ou fixtures |
| `CLAUDE_CONTEXT.txt` (racine) | 977 KB | `git rm --cached` ou déplacer |
| `apps/web/e2e-results.json` (3.4 MB) | 3.4 MB | `.gitignore` + `rm` |
| 48 PNG racine (dashboard-*, login-*, design-system-*, forgot-password-*) | ~5 MB total | Move to `docs/screenshots/` ou delete |
| 36 `.playwright-mcp/*.yml` (untracked) | — | `rm -r .playwright-mcp/` |
### 8.4 Sécurité hors-code
| Item | Action |
| ----------------------------------------- | ------------------------------------------------------ |
| `/docker/haproxy/certs/veza.pem` tracked | BFG purge history + rotate cert + K8s Secret |
| `/config/ssl/*.pem` tracked | Idem |
| `veza-backend-api/.env` tracked | `git rm --cached`, rotate dev secrets, audit team |
| CSP header absent | Middleware `SecurityHeaders` — ajouter |
| X-Frame-Options absent | Idem |
### 8.5 Incohérences doc↔code
| Item | Delta |
| ---------------------------------------------- | -------------------------------------------------- |
| `CLAUDE.md` : Vite 5 | Réel Vite 7.1.5 — bumper doc |
| `CLAUDE.md` : ES 8.11.0 partout | Réel ES 8.11.0 dev-only |
| `CLAUDE.md` : Go 1.25 | go.mod 1.25.0 ✅ ; `veza-backend-api/Dockerfile` 1.24 — bumper |
| `docs/API_REFERENCE.md` manuel 1022 LOC | 135 handlers — risque drift. OpenAPI typegen backend recommandé. |
| `VEZA_VERSIONS_ROADMAP.md` v0.9xx | VERSION = 1.0.7-rc1 — archive le roadmap |
| `docs/ASVS_CHECKLIST_v0.12.6.md` etc | Version obsolète. Refaire sur v1.0.7 ou archiver. |
| `docs/ENV_VARIABLES.md` mentionné | Pas trouvé en `ls docs/`. Créer. |
### 8.6 Patterns abandonnés ou à mi-chemin
1. **OpenAPI typegen frontend** : démarré (`api.ts` 6550 LOC régénéré) mais les **73 services frontend restent hand-written**. Finir la migration (memory entry : "orval recommended").
2. **OpenAPI typegen backend** : `docs/API_REFERENCE.md` manuel. Swagger infra (`swaggo/swag`) présente mais pas pleinement exploitée.
3. **Repository pattern** : `repositories/` (GORM-direct, 18 fichiers) mixé avec `services/` qui requêtent `gormDB` direct. Pas d'interfaces. Pattern mi-chemin.
4. **Architecture `core/` + `services/`** : pas de règle claire. À unifier ou à documenter explicitement quelles features vont où.
5. **Transactions** : 8 usages vs 37 tx manuels. Pattern moitié-fait.
---
## 9. Top 15 priorités — impact / effort
> **Mise à jour 2026-04-23** — colonne `Statut` ajoutée après la session cleanup tier 1/2/3 + BFG history rewrite. Voir §9.bis pour le détail des 3 false-positives identifiés pendant l'exécution.
Classement pour la suite (post-v1.0.7-rc1 → v1.0.7 final → v1.0.8).
| # | Priorité | Impact | Effort | Statut 2026-04-23 | Rationale / Preuve |
| --- | -------------------------------------------------------------------------------- | :----: | :-----: | :---------------- | -------------------------------------------------------------------------- |
| 1 | **Supprimer `api` 99 MB + binaires Go trackés racine + `uploads/*.mp3`** | 🔴 CRIT | XS (1h) | ✅ DONE | BFG pass 2026-04-23, 1.5G → 66M. Force-push stages 1+2 OK. |
| 2 | **Rotate TLS certs + supprimer `.pem` trackés + .env committed** | 🔴 CRIT | S (4h) | ✅ DONE | `.env*` + certs stripped via BFG. Keys regen, gitignorées. |
| 3 | **Transactions marketplace/subscription** | 🔴 CRIT | M (3j) | ✅ DONE | Commit `b5281bec``UpdateProductImages` + `SetProductLicenses` en tx. |
| 4 | **Context propagation : 31× `context.Background()` dans handlers** | 🔴 | S (1j) | ⚠️ FALSE-POSITIVE | 26/31 dans `*_test.go`, 5 legit (health probes + WS pumps). Voir §9.bis. |
| 5 | **Ajouter CSP + X-Frame-Options headers** | 🔴 | S (1j) | ⚠️ FALSE-POSITIVE | `middleware/security_headers.go` couvre déjà CSP + XFO + HSTS + CORP/COEP/COOP. Voir §9.bis. |
| 6 | **Pin MinIO `:latest` → tag daté** | 🔴 | XS (10min) | ✅ DONE | Commit `4310dbb7` — pinned `RELEASE.2025-09-07T16-13-09Z` × 4 compose files. |
| 7 | **Nettoyer `.playwright-mcp/*.yml` + 48 PNG racine + `CLAUDE_CONTEXT.txt` + dead reports apps/web/** | 🟡 | S (2h) | ✅ DONE | Commits `d12b901d` + `172581ff` + BFG pass. |
| 8 | **Terminer OpenAPI typegen** (frontend services + backend swaggo) | 🟡 | L (5j) | 📋 DEFERRED v1.0.8 | Memory entry, drift risk. `api.ts` 6550 LOC déjà là. Plan séparé requis. |
| 9 | **Supprimer 19 workflows `.disabled` (1676 LOC mort) OU réactiver utiles (SAST, DAST, openapi-lint)** | 🟡 | S (4h) | ✅ DONE | Archivés dans `docs/archive/workflows/` via commit `172581ff`. |
| 10 | **Consolider `RespondWithAppError` dupliqué** | 🟡 | S (1j) | ⚠️ FALSE-POSITIVE | `handlers/error_response.go:12` = wrapper intentionnel déléguant à `response/response.go:101`. Pas dupe. Voir §9.bis. |
| 11 | **Wirer `UserRateLimiter` configuré mais non appelé** | 🟡 | S (1j) | ✅ DONE | Commit `ebf3276d` — wired in `AuthMiddleware.RequireAuth()`. |
| 12 | **Supprimer `internal/repository/` (in-mem mock orphelin)** | 🟡 | XS | ✅ DONE | `user_repository.go` supprimé dans commit `172581ff`. |
| 13 | **Remove/archive `proto/chat/chat.proto` + `veza-common/src/chat.rs`** | 🟡 | XS | ✅ DONE | Commit `172581ff` — proto + `veza-common/{chat.rs, websocket.rs}` supprimés. |
| 14 | **Ajouter E2E Playwright en CI** | 🟡 | M (3j) | 📋 DEFERRED v1.0.8 | Playwright existe, SKIPPED_TESTS.md documenté, mais pas trigger CI. |
| 15 | **`docs/ENV_VARIABLES.md` — créer si manque, sync avec code** | 🟠 | S (1j) | 📝 PENDING (0.5j) | Seul item réel restant du top-15 avant tag v1.0.7 final. |
**Bilan** : 10 ✅ DONE · 3 ⚠️ FALSE-POSITIVE · 2 📋 DEFERRED v1.0.8 · 1 📝 PENDING (~0.5j).
### 9.1 "À supprimer sans regret"
- `infra/docker-compose.lab.yml` (DEPRECATED Feb 2026)
- `scripts/align-8px-grid.py`, `auto_migrate_tailwind_colors*.py` (tailwind migration faite)
- 48 PNG racine
- 36 `.playwright-mcp/*.yml`
- 19 `.disabled` workflows
- Binaires Go trackés
- 44 fichiers audio `.mp3/.wav` dans `veza-backend-api/uploads/`
- `CLAUDE_CONTEXT.txt` racine
- `VEZA_VERSIONS_ROADMAP.md` (v0.9xx historique)
- `generate_page_fix_prompts.sh` racine (42 KB, Mar 26)
- `output.txt`, `build-archive.log` racine
- `apps/web/{e2e-results.json, lint_comprehensive.json, ts_errors.log, AUDIT_ISSUES.json}`
- `internal/repository/` (orphelin)
- `proto/chat/chat.proto` + types `veza-common/src/chat.rs`
- `apps/web/src/components/ui/{hover-card,dropdown-menu,optimized-image}/` orphelins
- ~~`docs/ASVS_CHECKLIST_v0.12.6.md` + `docs/PENTEST_REPORT_VEZA_v0.12.6.md` + `docs/REMEDIATION_MATRIX_v0.12.6.md`~~ ✅ archivés dans `docs/archive/` (2026-04-23)
### 9.2 "À finir avant de commencer quoi que ce soit de nouveau"
> **Mise à jour 2026-04-23** — la liste originale (#1, #2, #3, #4, #5, #7, #8, #9) a été traitée en une session, sauf les 3 false-positives §9.bis et les 2 deferrals. Ne reste qu'un item (§9.3).
1. ~~**Cleanup repo** (#1, #2, #7, #9)~~ — ✅ fait, 1 session 2026-04-23.
2. ~~**Transactions manquantes** (#3)~~ — ✅ fait, commit `b5281bec`.
3. ~~**Context propagation** (#4)~~ — ⚠️ false-positive, pas de travail à faire (§9.bis).
4. ~~**Security headers** (#5)~~ — ⚠️ false-positive, middleware déjà complet (§9.bis).
5. **OpenAPI typegen** (#8) — 📋 deferred v1.0.8, plan séparé requis.
### 9.bis Corrections post-tier 2 (2026-04-23)
Trois items du top-15 ont été reclassifiés après inspection directe du code :
**#4 — "Context propagation : 31× `context.Background()` dans handlers"**
Grep réel : 31 hits dans `internal/handlers/`, mais **26 dans des fichiers `_test.go`** (legit, setup tests). Les 5 hits non-test sont tous légitimes :
- `handlers/status_handler.go:184` — probe health externe, `ctx` dédié 400ms
- `handlers/playback_websocket_handler.go:{142,218,245}` — pumps WebSocket (doivent survivre au cycle HTTP request, pas de parent ctx disponible post-Upgrade)
- `handlers/health.go:422` — health check 5s, `ctx` dédié
Le chiffre "31" masquait des patterns corrects. **Aucun handler qui défait un timeout middleware**. Pas de travail à faire.
**#5 — "Ajouter CSP + X-Frame-Options headers"**
Vérification `veza-backend-api/internal/middleware/security_headers.go` : le middleware existe déjà (BE-SEC-011 + MOD-P2-005) et couvre **tous** les headers OWASP A05 recommandés :
- `Strict-Transport-Security` (prod only)
- `X-Frame-Options: DENY` (default) / `SAMEORIGIN` (Swagger)
- `Content-Security-Policy` — strict `default-src 'none'` par défaut, override Swagger
- `X-Content-Type-Options: nosniff`
- `X-XSS-Protection`, `Referrer-Policy`, `Permissions-Policy`
- `X-Permitted-Cross-Domain-Policies: none`
- `Cross-Origin-{Embedder,Opener,Resource}-Policy`
Audit erroné. Pas de travail à faire.
**#10 — "Consolider `RespondWithAppError` dupliqué"**
Vérification :
- `internal/response/response.go:101` = implémentation réelle (17 lignes)
- `internal/handlers/error_response.go:12` = wrapper **intentionnel** de 3 lignes qui délègue à `response.RespondWithAppError(c, appErr)`. Commenté `// Délègue au package response pour éviter duplication`.
Le wrapper existe pour permettre aux handlers d'importer depuis le package `handlers` sans traverser la frontière `response/` — pattern de couplage sain. Pas une duplication à consolider. Pas de travail à faire.
### 9.3 Chemin critique vers v1.0.7 final stable
> **Mise à jour 2026-04-23** — le plan 5-jours original a été compressé en 1 session (cleanup + BFG + transactions + wiring). Ne reste que l'item doc.
| Jour (historique) | Tâches planifiées v1 | Statut 2026-04-23 |
| :-: | --- | --- |
| J1 | Items #1, #2, #6, #7 — cleanup + rotation + BFG + retag | ✅ DONE |
| J2 | Items #4, #10, #12, #13 | ⚠️ #4/#10 false-positive · ✅ #12/#13 done |
| J3-4 | Item #3 — transactions marketplace | ✅ DONE (commit `b5281bec`) |
| J5 | Items #5, #11, #15 + tag `v1.0.7` | ⚠️ #5 false-positive · ✅ #11 done · 📝 #15 reste (0.5j) |
**Reste à faire avant tag `v1.0.7` final** : item #15 (`docs/ENV_VARIABLES.md` sync) — **0.5j**. Et un quick-win 5min : ajouter `HLS_STREAMING` à `.env.template` (cf. FUNCTIONAL_AUDIT §4 stabilité item 5).
Ensuite v1.0.8 : OpenAPI typegen (#8, 5j), E2E CI (#14, 3j), item G subscription `pending_payment` (parké dans `docs/audit-2026-04/v107-plan.md`), wire MinIO/S3 dans path upload (2-3j, cf. FUNCTIONAL §4 item 2), STUN/TURN WebRTC si calls public (1-2j).
---
## 10. Verdict final
> **v2 (2026-04-20)** — application solide, dépôt sale.
> **v3 (2026-04-23, post-cleanup + BFG)****application solide, dépôt propre**.
- **Code applicatif** : mature, testé (286 tests front + 364 back), sécurisé (gitleaks/govulncheck/trivy, JWT RS256, 2FA, OAuth, CORS strict, CSRF, DDoS rate limit), plomberie monétaire auditée (ledger-health gauges, reconciliation, idempotency, reverse-charge). **Transactions marketplace `DELETE+loop` atomiques depuis `b5281bec`**. **UserRateLimiter wired dans `AuthMiddleware` depuis `ebf3276d`**.
- **Code infra** : 3 variants Dockerfile (dev/prod), K8s avec disaster recovery, 5 workflows CI actifs (+ 19 disabled archivés `docs/archive/workflows/`), 6 compose env pinned (MinIO daté), HAProxy blue-green.
- **Hygiène repo** : 2.3 GB → **66 MB** `.git` après BFG 2026-04-23 (97%). Binaires Go, PNG racine, `.playwright-mcp`, audio uploads, `.env*`, TLS certs, kubectl vendoré, builds Incus, reports lint : **tous stripped de l'historique** + ajoutés à `.gitignore` (blocks J1 + J2 + J3).
**Score** : v1 disait "Moyen-Haute dette". v2 : "Basse dette code / Haute dette hygiène". **v3 : dette résiduelle mineure** — 1 item pending (`docs/ENV_VARIABLES.md`, 0.5j) + 3 false-positives classés + 2 deferrals v1.0.8.
**En une phrase** : **`v1.0.7-rc1` est prêt à devenir `v1.0.7` final** dès que `docs/ENV_VARIABLES.md` est synchronisé avec les 99 env vars du code. Le reste (OpenAPI typegen, E2E CI, MinIO upload path, STUN/TURN) part sur v1.0.8 avec des plans séparés.
---
## Annexe — diff v1 ↔ v2 ↔ v3
| Thème | v1 (2026-04-14) | v2 (2026-04-20) | v3 (2026-04-23, post-cleanup + BFG) |
| -------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| HEAD | `45662aad1` (v1.0.0-mvp-24-g45662aad1) | `89a52944e` (v1.0.7-rc1) | post-BFG : main `6d51f52a`, chore `b5281bec` |
| Finding "chemin critique v1.0.5 public-ready"| 6 items listés | **Tous les 6 traités** (v1.0.5 → v1.0.7-rc1, 50+ commits) | — |
| 🔴 Player/écoute audio | Bloqueur | Résolu — endpoint `/tracks/:id/stream` + Range bypass | — |
| 🔴 IsVerified hardcoded | Bloqueur | Résolu — `core/auth/service.go:200` `IsVerified: false` | — |
| 🟡 SMTP silent fail | Bloqueur | Résolu — schema unifié + MailHog default | — |
| 🟡 Marketplace dev bypass | Bloqueur | Résolu — fail-closed prod via `Config.Validate:908-910` | — |
| 🟡 Refund stub | Bloqueur | Résolu — 3-phase + idempotency + webhook reverse-charge | — |
| 🟡 Chat multi-instance silent | Bloqueur | Résolu — log ERROR loud `chat_pubsub.go:23-27` | — |
| 🟡 Maintenance mode in-memory | Bloqueur | Résolu — persisté `platform_settings` TTL 10s | — |
| 🔵 Reconciliation Hyperswitch | Absent | **Nouveau**`reconcile_hyperswitch.go:55-150` | — |
| 🔵 Webhook raw payload audit | Absent | **Nouveau**`webhook_log.go:34-80` + cleanup 90j | — |
| 🔵 Ledger-health metrics | Absent | **Nouveau** — 5 gauges + 3 alertes + Grafana | — |
| 🔵 Stripe Connect reversal async | Absent | **Nouveau**`reversal_worker.go:12-180` | — |
| 🔵 Self-service creator upgrade | Absent | **Nouveau**`POST /users/me/upgrade-creator` | — |
| Hygiène `.git` 2.3 GB | Bloqueur | **Non traité** | ✅ **66 MB après BFG** (97%) |
| Hygiène binaires tracked | 3 binaires | 1 reste (`api` 99 MB racine) | ✅ **0 binaires** (BFG pass + `.gitignore` J3) |
| Hygiène `uploads/*.mp3` 44 fichiers | Présent | **Non traité** | ✅ **stripped** (BFG pass, `uploads/` gitignoré J2) |
| Hygiène 54 PNG racine | Présent | 48 restent | ✅ **stripped** (BFG pass, patterns gitignorés J2+J3) |
| TLS certs committés + `.env*` | Présent | Présent | ✅ **stripped** (BFG pass) |
| Transactions marketplace | Non auditée | 🔴 CRIT flaggée | ✅ **fixées** (commit `b5281bec`) |
| UserRateLimiter | Non mentionné | Configuré mais non câblé | ✅ **wiré** (commit `ebf3276d`) |
| Orphelin `internal/repository/` | Non mentionné | Flaggé | ✅ **supprimé** (commit `172581ff`) |
| Orphelins Rust (`proto/chat`, `veza-common/{chat,ws}.rs`) | Non mentionné | Flaggé | ✅ **supprimés** (commit `172581ff`) |
| Runbooks k8s outdated (chat Rust) | 7+ runbooks | **0 référence** — clean | — |
| CLAUDE.md précis | Faux | **À jour** sauf Vite 5→7 | — |
| Site Docusaurus `ORIGIN/` | À réécrire | **22 fichiers FOSSILE encore** — à archiver | (hors scope cleanup) |
| Workflows CI | `.github/workflows/*` non consolidé | Consolidé (`ci.yml`) + **19 disabled qui traînent** | ✅ **19 archivés** dans `docs/archive/workflows/` |
| `docs/audit-2026-04/` | Absent | **Nouveau** — axis-1-correctness + v107-plan | — |
**Score global** : v1 "Moyen-Haute dette" → v2 "Basse dette code / Haute dette hygiène" → **v3 "dette résiduelle mineure" (1 item pending, 3 false-positives classés, 2 deferrals v1.0.8)**.
---
*Généré par Claude Code Opus 4.7 (1M context, /effort max, /plan) — 5 agents Explore parallèles (frontend, backend Go, Rust stream, infra/DevOps, dette transverse) + mesures macro directes (du, ls, git ls-files) + lecture `CHANGELOG.md` v1.0.5→v1.0.7-rc1 + `docs/audit-2026-04/v107-plan.md`. Cross-référencé avec [FUNCTIONAL_AUDIT.md v2](FUNCTIONAL_AUDIT.md) pour les verdicts fonctionnels.*

View file

@ -0,0 +1,702 @@
# AUDIT TECHNIQUE — VEZA MONOREPO
| Champ | Valeur |
|-------|--------|
| **Date** | 2026-02-22 |
| **Auditeur** | Claude 4.6 Opus (IA) — mandat due diligence |
| **Version analysée** | v0.402, main (HEAD+49 commits non poussés) |
| **Périmètre** | Backend Go, Chat Server Rust, Stream Server Rust, Frontend React, Infra Docker/CI |
| **Méthodologie** | Analyse statique du code source, 6 passes d'exploration |
| **Classification** | Confidentiel — Usage interne |
---
## EXECUTIVE SUMMARY
### Verdict global
Veza est un projet **ambitieux et structurellement bien pensé** pour un effort solo/micro-équipe. L'architecture backend Go est la pièce la plus mature : séparation handler → service → repository, middleware stack complète, couverture de tests proche de 1:1. Le frontend React est extensif (~131K LOC source) avec un design system cohérent (SUMI), Storybook-driven development, et 288 stories.
**Cependant, le projet n'est pas prêt pour la production.** La vélocité affichée (345+ features, 12 releases en ~3 mois) masque une réalité : de nombreuses features sont partiellement implémentées (frontend mock, backend stub, ou flux E2E non connecté). Les services Rust compilent mais ne sont pas intégrés (gRPC = stub, boot mode = chat/stream OFF). L'infrastructure CI/CD contient des défauts critiques (pipeline CD non fonctionnel, secrets en clair, versions Go incohérentes).
### Top 5 risques
| # | Risque | Gravité |
|---|--------|---------|
| 1 | **Pipeline CD non fonctionnel** — Les conditions `secrets.*` dans les `if` GitHub Actions ne s'évaluent jamais. Les étapes push, sign, deploy ne s'exécutent pas. | CRITIQUE |
| 2 | **Authentification HLS/WebSocket cassée**`TokenStorage.getAccessToken()` retourne toujours `null` (cookies httpOnly). Les clients HLS et WebSocket ne peuvent pas s'authentifier. | CRITIQUE |
| 3 | **Redis sans mot de passe en production**`docker-compose.prod.yml` ne configure aucune authentification Redis. | ÉLEVÉ |
| 4 | **Rate limiter en mémoire** — Ne fonctionne pas en multi-instance. Brute force possible en prod scalée. | ÉLEVÉ |
| 5 | **Services Rust non intégrés** — Chat et Stream servers compilent mais tournent en "boot mode" (OFF). 21.5% des 600 features annoncées sont réellement fonctionnelles. | ÉLEVÉ |
### Top 5 forces
| # | Force |
|---|-------|
| 1 | **Architecture backend Go exemplaire** — Séparation claire des responsabilités, middleware stack complète (23 middlewares), ratio test/code 0.97:1 |
| 2 | **Sécurité auth solide** — Tokens httpOnly, access token 5min, bcrypt cost 12, CSRF timing-safe, validation JWT stricte (iss/aud/exp/algo) |
| 3 | **Design system cohérent** — SUMI Design System v2.0, 882 lignes de tokens CSS, Storybook-first, 288 stories |
| 4 | **Infrastructure de qualité** — CI multi-pipeline, Dependabot, security scanning, Dockerfiles multi-stage, utilisateur non-root |
| 5 | **Documentation extensive** — 63 docs frontend, scope control par version, CHANGELOG structuré, FEATURE_STATUS tracé |
### Recommandation go/no-go
**NO-GO pour production en l'état.** Conditionnel à 4-6 semaines de stabilisation ciblée (voir Phase 1-2 du plan d'action). Le code est de qualité suffisante pour être corrigé, pas réécrit.
---
## 1⃣ CARTOGRAPHIE GLOBALE
### 1.1 Stack réelle
| Élément | Constaté dans le code |
|---------|----------------------|
| **Go** | 1.24.0 (`go.work`), mais Dockerfile.production utilise 1.23-alpine — **incohérence** |
| **Rust** | Stable channel (`rust-toolchain.toml`), Edition 2021 |
| **Node.js** | 20 (CI workflows), npm 10.9.2 (`packageManager`) |
| **React** | 18.2.0 |
| **Vite** | 7.1.5 |
| **TypeScript** | 5.3.3 (package.json) mais 5.9.3 (root devDependencies) — **incohérence** |
| **Tailwind CSS** | 4.0.0 (CSS-first config) |
| **Framework Go** | Gin 1.11.0 (dernière stable, maintenu) |
| **ORM Go** | GORM 1.30.0 + lib/pq 1.10.9 (parameterized queries) |
| **Framework Rust** | Axum 0.8 (chat + stream), Tokio 1.35 |
| **SQLx** | 0.8 (Rust services) |
| **PostgreSQL** | 16-alpine (dev/prod), 15-alpine (test/hybrid) — **incohérence** |
| **Redis** | 7 (docker-compose), go-redis/v9 9.16.0 |
| **RabbitMQ** | 3-management-alpine |
| **Auth** | JWT HS256 via `golang-jwt/jwt/v5`, access 5min, refresh 14j, remember-me 30j |
| **Paiement** | Hyperswitch via `@juspay-tech/hyper-js`, SDK frontend uniquement, mode test |
| **Streaming** | HLS prévu mais **désactivé** (`HLS_STREAMING=false`), service stub |
| **WebSocket** | Gorilla (Go), Axum WS (Rust), native WebSocket API (frontend) |
| **WebRTC** | Code présent dans stream-server mais **commenté/désactivé** |
| **CI/CD** | GitHub Actions — 12 workflows |
| **Containerisation** | Docker multi-stage, images alpine, utilisateur non-root |
| **Monitoring** | Prometheus configuré, Grafana référencé, Sentry intégré, zap structured logging |
| **Monorepo** | Turborepo + npm workspaces + Go workspace |
### 1.2 Organisation du monorepo
| Répertoire | Rôle réel | Fichiers | LOC |
|-----------|-----------|----------|-----|
| `veza-backend-api/` | API REST Go — cœur fonctionnel du produit | 671 .go | 174,022 |
| `veza-chat-server/` | Serveur chat Rust — compile, non intégré | 78 .rs | ~60,000* |
| `veza-stream-server/` | Serveur streaming Rust — compile, non intégré | 113 .rs | ~80,000* |
| `apps/web/` | Frontend React/Vite — interface utilisateur | 1,837 .ts/.tsx | ~206,000 |
| `veza-docs/` | Site Docusaurus — squelette non alimenté | ~20 | ~500 |
| `docs/` | Documentation projet/versioning | 332 .md | ~15,000 |
| `scripts/` | Scripts utilitaires (audit, migration, deploy) | 85+ | ~5,000 |
| `.github/` | CI/CD workflows + templates | 12 workflows | ~800 |
| `config/` | Prometheus, métriques, SSL | 5 | ~100 |
| `infra/` | docker-compose lab | 1 | ~50 |
| `make/` | Makefile modulaire | 11 .mk | ~800 |
| `dev-environment/` | Templates de services | ~10 | ~400 |
| `fixtures/` | Package npm vide | 5 | ~50 |
| `packages/` | Shared packages — **vide** | 0 | 0 |
*LOC Rust estimée (total 352K inclut le code généré gRPC/protobuf)
**Packages orphelins :**
- `packages/` — déclaré dans npm workspaces mais vide
- `fixtures/` — package npm avec `vitest.config.ts` mais aucun test
- `veza-docs/` — Docusaurus configuré mais non alimenté
**Packages fantômes :**
- `veza-backend-api/internal/api/archive/api_manager.go` — 789 lignes de code commenté/TODO, jamais importé
- `dev-environment/templates/` — templates de génération de code non utilisés par un outil
**Duplications cross-packages :**
- JWT validation implémentée 3 fois (Go `jwt_service.go`, Rust chat `jwt_manager.rs`, Rust stream `token_validator.rs`)
- Configuration loading implémentée 3 fois avec des patterns différents
- gRPC protobuf généré dupliqué entre chat et stream servers
### 1.3 Dépendances critiques
#### Backend Go (44 dépendances directes)
| Dépendance | Version | Statut | Risque |
|-----------|---------|--------|--------|
| `gin-gonic/gin` | 1.11.0 | Maintenu activement | Faible |
| `gorm.io/gorm` | 1.30.0 | Maintenu activement | Faible |
| `golang-jwt/jwt/v5` | 5.3.0 | Maintenu | Faible |
| `redis/go-redis/v9` | 9.16.0 | Maintenu | Faible |
| `gorilla/websocket` | 1.5.3 | **Archivé** (décembre 2024) | MOYEN — migrer vers `nhooyr.io/websocket` |
| `lib/pq` | 1.10.9 | En maintenance minimale | Faible (GORM l'utilise via driver) |
| `swaggo/swag` | 1.16.6 | Maintenu | Faible |
| `sony/gobreaker` | 1.0.0 | Maintenu | Faible |
| `getsentry/sentry-go` | 0.40.0 | Maintenu | Faible |
| `testcontainers-go` | 0.33.0 | Maintenu | Faible |
#### Frontend React (dépendances majeures)
| Dépendance | Version | Statut | Risque |
|-----------|---------|--------|--------|
| `react` | 18.2.0 | **React 19 disponible** — 1 majeure de retard | MOYEN |
| `@tanstack/react-query` | 5.17.0 | Maintenu | Faible |
| `zustand` | 4.5.0 | Maintenu | Faible |
| `msw` | 2.11.2 | Maintenu | Faible |
| `dompurify` | Utilisé via sanitize.ts | Maintenu | Faible |
| `@juspay-tech/hyper-js` | Hyperswitch SDK | Niche — petit écosystème | MOYEN |
#### Rust (dépendances clés)
| Dépendance | Version | Risque |
|-----------|---------|--------|
| `axum` | 0.8 | Faible — maintenu par Tokio |
| `sqlx` | 0.8 | Faible — maintenu |
| `jsonwebtoken` | 10 | Faible |
| `tonic` | 0.11 | Faible — gRPC bien maintenu |
| `lapin` | 2.3 | MOYEN — RabbitMQ Rust, communauté petite |
| `symphonia` | 0.5 | MOYEN — audio processing, niche |
### 1.4 Schéma des flux
#### Auth flow
```
Browser → POST /api/v1/auth/register → AuthHandler → AuthService → GORM → PostgreSQL
→ POST /api/v1/auth/login → AuthHandler → PasswordService.VerifyPassword → bcrypt
→ Set-Cookie: access_token (httpOnly, 5min)
→ Set-Cookie: refresh_token (httpOnly, 14j)
→ POST /api/v1/auth/refresh → Cookie → JWTService.ValidateToken → TokenVersion check → New tokens
→ POST /api/v1/auth/oauth/:provider → OAuthService → Google/GitHub → JWT
```
**SPOF :** PostgreSQL (session lookup per request), Redis (CSRF tokens)
**Timeout :** 30s request timeout (middleware), context propagation
**Retry :** Pas de retry sur DB failure
**Race condition :** Token version increment non transactionnel — deux refresh simultanés pourraient invalider l'un l'autre
#### Payment flow
```
Frontend → POST /api/v1/marketplace/checkout → MarketplaceHandler → HyperswitchService
→ Hyperswitch API → Create PaymentIntent → client_secret
→ Frontend → Hyperswitch SDK → Card form → Confirm payment
→ Hyperswitch → Webhook → POST /api/v1/webhooks/hyperswitch (?)
→ MarketplaceService → Update order status
```
**SPOF :** Hyperswitch API (externe)
**Risque critique :** Le handler de webhook entrant Hyperswitch n'a pas été trouvé dans `webhook_handlers.go` (ce fichier ne gère que les webhooks sortants). La vérification de signature webhook est potentiellement absente.
**Timeout :** Non vérifié pour les appels Hyperswitch
**Idempotence :** Non vérifiée pour les webhooks de paiement
#### Chat flow (théorique — non intégré)
```
Frontend → WebSocket /ws → Chat Server (Rust/Axum) → JWT validation → Hub
→ Message → SQLx → PostgreSQL (chat DB séparée)
→ Broadcast → Connected clients
```
**État actuel :** Boot mode — chat server OFF. Frontend utilise MSW mocks.
#### Stream flow (théorique — non intégré)
```
Frontend → GET /stream/hls/:track_id/playlist.m3u8 → Stream Server (Rust/Axum)
→ JWT validation (cassée — token null) → HLS segments → Player
```
**État actuel :** Stream server OFF. HLS désactivé. Frontend fallback sur des URLs directes.
---
## 2⃣ CE QUE LE PRODUIT PERMET RÉELLEMENT
### 2.1 Classification des features
#### ✅ Fonctionnelles (flux complet front + back + DB)
1. **Authentication** — Register/Login/Logout avec JWT httpOnly cookies
2. **2FA TOTP** — Activation, vérification, codes de récupération
3. **OAuth** — Google, GitHub (Discord/Spotify : code présent mais non fonctionnel)
4. **Profils utilisateur** — CRUD, avatar, banner, liens sociaux, profil privé
5. **Upload audio** — Validation magic bytes, ClamAV, métadonnées
6. **CRUD Tracks** — Création, édition, suppression, métadonnées enrichies (BPM, key, lyrics, tags)
7. **Playlists** — CRUD, collaboration, partage, recommandations
8. **Dashboard** — Vue d'ensemble utilisateur
9. **Sessions** — Liste, révocation
10. **Settings** — Profil, sécurité, notifications, préférences
11. **Marketplace** — Catalogue produits, panier, wishlist
12. **Search** — Recherche full-text avec pg_trgm, filtres
13. **Social posts** — CRUD, likes, commentaires (feed basique)
14. **RBAC** — Rôles utilisateur, middleware d'autorisation
#### ⚠️ Partiellement implémentées
| Feature | Backend | Frontend | Écart |
|---------|---------|----------|-------|
| **Checkout Hyperswitch** | Handler + Hyperswitch SDK | Formulaire paiement | Webhook entrant non trouvé, mode test uniquement |
| **Promo codes** | Migration 099-100, handler | Modal + cart integration | En cours (fichiers modifiés dans git status) |
| **Notifications** | Service + push web | Composants UI | Backend OK, frontend MSW pour certaines routes |
| **Analytics** | Handler + service (7% complet selon audit interne) | Dashboard composants | Données réelles partielles |
| **Admin panel** | Routes protégées | Pages admin | Fonctionnalités limitées |
| **Webhooks** | CRUD outbound | Developer UI | Pas de delivery engine visible |
| **Gear/Inventory** | Handler | Composants | Backend minimal |
| **Live streaming** | Handler | Composants | Backend stub |
| **Trending** | TrendingService | Feed explore | Algorithme basique |
#### 👻 Fantômes (déclarées mais absentes ou stub)
| Feature | Déclarée dans | Réalité |
|---------|--------------|---------|
| **HLS Streaming** | FEATURE_STATUS ("operational") | `HLS_STREAMING=false`, stream server OFF, `getHLSXhrSetup()` retourne token null |
| **WebRTC Audio Calls** | CHANGELOG v0.303 | Code commenté/désactivé dans stream-server |
| **OAuth Discord/Spotify** | FEATURE_STATUS ("operational") | Audit interne confirme : non implémentés |
| **Chat temps réel** | FEATURE_STATUS ("operational") | Chat server en boot mode (OFF), frontend MSW |
| **gRPC inter-services** | Architecture déclarée | Stub — protobuf généré mais endpoints non connectés |
#### 💀 Mortes (code présent, jamais appelé)
| Code mort | Fichier | LOC |
|-----------|---------|-----|
| `api_manager.go` | `internal/api/archive/api_manager.go` | 789 |
| `docs.go` (Swagger généré) | `internal/handlers/docs/docs.go` | 5,482 |
| `GenerateJWT` dans PasswordService | `internal/services/password_service.go:249` | ~20 (méthode sans iss/aud, potentiellement dangereuse si appelée) |
| `TokenStorage.getAccessToken()` | `apps/web/src/services/tokenStorage.ts` | ~107 (tout le fichier est un no-op) |
| `isTokenExpiringSoon()` | `apps/web/src/services/tokenRefresh.ts` | ~30 (retourne toujours true) |
| `MOCK_PURCHASES` | `apps/web/src/services/commerceService.ts` | ~50 (données mock retournées en production) |
| `requestRefund()` | `apps/web/src/services/commerceService.ts` | ~10 (no-op, retourne toujours `{success: true}`) |
#### 🧪 Expérimentales abandonnées
| Feature | Traces |
|---------|--------|
| **Éducation/Gamification** | Supprimés du code, mentionnés dans FEATURE_STATUS comme "permanently deleted" |
| **veza-mobile** | Mentionné dans FEATURE_STATUS comme abandonné |
| **packages/design-system** | Répertoire `packages/` vide, design system migré dans `apps/web/src/index.css` |
### 2.2 Incohérences produit/code
| Source | Affirme | Réalité code |
|--------|---------|-------------|
| `docs/FEATURE_STATUS.md` | "19 features operational" | ~14 véritablement fonctionnelles E2E, 5 partielles ou fantômes |
| `docs/FEATURE_STATUS.md` | "HLS_STREAMING = true, operational" | `HLS_STREAMING=false`, service OFF, auth cassée |
| `docs/FEATURE_STATUS.md` | "OAuth Discord + Spotify operational" | Audit interne confirme non implémentés |
| `CHANGELOG.md` v0.303 | "WebRTC audio calls 1-to-1" | Code commenté dans stream-server |
| `CHANGELOG.md` v0.402 | "Checkout Hyperswitch production-ready" | Mode test, webhook entrant non trouvé |
| Audit interne (103) | Score 32/100, 21.5% features done | FEATURE_STATUS liste 19 features "operational" |
| `V0_101_RELEASE_SCOPE.md` | "All services must be running together" | Boot mode = chat/stream/RabbitMQ/ClamAV OFF |
---
## 3⃣ VALIDATION FONCTIONNELLE
### 3.1 Couverture de tests
| Service | Fichiers test | LOC test | LOC source | Ratio | Commentaire |
|---------|--------------|----------|-----------|-------|-------------|
| **Go backend** | 264 | 85,455 | 87,930 | **0.97:1** | Excellent. Tests unitaires + intégration + sécurité |
| **Rust chat** | ~28 modules | ~5,000* | ~55,000* | ~0.09:1 | Faible. Principalement des tests unitaires inline |
| **Rust stream** | ~30 modules | ~8,000* | ~72,000* | ~0.11:1 | Faible. Tests de charge présents mais basiques |
| **Frontend** | 274 | 58,816 | 130,976 | **0.45:1** | Correct. Tests composants Vitest + Storybook tests |
| **Stories** | 288 | 15,987 | — | — | Bonne couverture Storybook |
*Estimé à partir des 352K LOC Rust totales incluant le code généré
**Tests E2E :** Playwright configuré (5 configs : smoke, storybook, visual, main, patch). Scénarios E2E dans `ci.yml` avec docker-compose full stack.
**Tests de sécurité :** Présents dans Go (`tests/security/authorization_test.go`, `injection_attack_test.go`).
**Mocks vs API réelle :** 100% du frontend teste contre MSW. Aucun test frontend contre l'API réelle (sauf E2E).
### 3.2 Points de rupture identifiés
| Scénario | Impact | Mitigation existante |
|----------|--------|---------------------|
| Redis tombe | CSRF cassé → toutes les mutations échouent (503) | Aucune — Redis est SPOF pour CSRF en prod |
| 10K tracks par utilisateur | Pagination cursor OK, mais pas de limite `max` documentée | Pagination offset + limit avec max configurable |
| Fichier audio 10GB | `MaxUploadSize` configurable, validation taille | Oui — configurable dans env |
| 1000 WebSocket simultanées | Chat server non testé sous charge en intégration | Load testing basique dans stream server |
| Webhook Hyperswitch replay | Handler webhook entrant non trouvé | **RISQUE** — pas d'idempotence vérifiable |
| Token expiré mid-session | Proactive refresh toutes les 4 min + retry 401 avec queue | Robuste — bien implémenté |
| Migration partielle | Pas de transaction wrapping dans les migrations SQL | **RISQUE** — état DB incohérent possible |
| 2 refresh simultanés | Token version increment non-atomic | **RISQUE** — race condition possible |
---
## 4⃣ REGISTRE DES VULNÉRABILITÉS
| ID | Catégorie | Gravité | Fichier(s) | Description | Impact | Correctif | Effort |
|----|-----------|---------|-----------|-------------|--------|-----------|--------|
| VEZA-SEC-001 | A05 Misconfig | **CRITIQUE** | `.github/workflows/cd.yml` | Conditions `secrets.*` dans `if` GHA ne s'évaluent jamais → pipeline CD non fonctionnel | Aucun déploiement automatisé ne fonctionne | Utiliser `vars.*` ou étape de vérification séparée | S |
| VEZA-SEC-002 | A07 Auth | **CRITIQUE** | `apps/web/src/services/tokenStorage.ts`, `hlsService.ts`, `websocket.ts` | `getAccessToken()` retourne `null` → HLS et WebSocket ne peuvent pas s'authentifier | Streaming non protégé ou non fonctionnel | Implémenter auth par cookie pour WS/HLS ou endpoint de stream token | M |
| VEZA-SEC-003 | A05 Misconfig | **CRITIQUE** | `docker-compose.hybrid.yml` | `network_mode: host` + Grafana password `admin` par défaut | Infrastructure accessible depuis le réseau sans auth | Supprimer network_mode host, forcer mot de passe | S |
| VEZA-SEC-004 | A05 Misconfig | **ÉLEVÉ** | `docker-compose.prod.yml` | Redis sans authentification en production | Cache compromis → session hijacking, data poisoning | Ajouter `--requirepass` et `REDIS_PASSWORD` | S |
| VEZA-SEC-005 | A01 Access | **ÉLEVÉ** | `docker-compose.prod.yml:217-220` | Stream server manque `JWT_SECRET` en prod compose | Service accepte potentiellement des requêtes non authentifiées | Ajouter `JWT_SECRET` dans la config stream-server | S |
| VEZA-SEC-006 | A04 Design | **ÉLEVÉ** | `internal/middleware/ratelimit.go` | Rate limiter in-memory → ne fonctionne pas multi-instance | Brute force multiplié par nombre d'instances | Migrer vers rate limiting Redis | M |
| VEZA-SEC-007 | A02 Crypto | **ÉLEVÉ** | `.github/workflows/ci.yml:248,287` | Mot de passe de test E2E en clair dans le workflow | Credential leakage si repo public | Migrer vers GitHub Secrets | S |
| VEZA-SEC-008 | A01 Access | **MOYEN** | `internal/handlers/upload.go:308-326` | `GetUploadStatus` — IDOR, pas de vérification d'ownership | Un utilisateur authentifié peut voir le statut de n'importe quel upload | Ajouter check `upload.UserID == currentUserID` | S |
| VEZA-SEC-009 | A10 SSRF | **MOYEN** | `internal/handlers/webhook_handlers.go:69` | URL de webhook accepte tout schéma (file://, http://169.254.x.x) | SSRF via webhook delivery | Valider schéma (https only), bloquer IPs privées | S |
| VEZA-SEC-010 | A08 Integrity | **MOYEN** | Non trouvé | Webhook entrant Hyperswitch — handler non identifié, vérification de signature incertaine | Webhooks de paiement potentiellement non vérifiés | Vérifier/implémenter vérification HMAC-SHA256 | M |
| VEZA-SEC-011 | A05 Misconfig | **MOYEN** | `cmd/api/main.go:8` | `import _ "net/http/pprof"` en production | Profiling endpoints accessibles si DefaultServeMux exposé | Conditionner import au mode dev | S |
| VEZA-SEC-012 | A02 Crypto | **MOYEN** | `internal/services/password_service.go:92-95` | Reset tokens stockés en clair dans PostgreSQL | Si DB compromise, tous les tokens de reset actifs sont exposés | Stocker le hash SHA-256 du token | S |
| VEZA-SEC-013 | A07 Auth | **MOYEN** | `internal/middleware/auth.go:312-406` | `OptionalAuth` ne vérifie pas la correspondance session/user | Session hijacking silencieux sur les routes optionnelles | Ajouter vérification `session.UserID == tokenUserID` | S |
| VEZA-SEC-014 | A04 Design | **MOYEN** | `internal/middleware/csrf.go:153` | Un seul token CSRF par utilisateur → multi-onglet cassé | UX dégradée, utilisateurs forcés de rafraîchir | Implémenter pool de tokens ou token par session | M |
| VEZA-SEC-015 | A05 Misconfig | **MOYEN** | `docker-compose.staging.yml:64` | `JWT_SECRET=${STAGING_JWT_SECRET}` sans check `?` → peut être vide | Staging potentiellement sans validation JWT | Ajouter `:?error message` | S |
| VEZA-SEC-016 | A01 Access | **MOYEN** | `docker-compose.staging.yml` | Ports backend/frontend exposés directement sans reverse proxy | Pas de TLS termination, pas de WAF | Ajouter HAProxy comme en prod | M |
| VEZA-SEC-017 | A09 Logging | **MOYEN** | Multiples fichiers | 15+ `fmt.Printf` dans le code production (upload_validator.go, router.go) | Bypass du logging structuré, potentielle fuite d'info | Remplacer par `logger.Debug()` | S |
| VEZA-SEC-018 | A07 Auth | **FAIBLE** | `apps/web/src/features/auth/store/authStore.ts:352` | `isAuthenticated` persisté dans localStorage | XSS → bypass des guards UI (backend protège toujours) | Utiliser sessionStorage ou mémoire uniquement | S |
| VEZA-SEC-019 | A02 Crypto | **FAIBLE** | `internal/services/password_service.go:150` | Coût bcrypt hardcodé `12` au lieu de la constante `bcryptCost` | Risque d'incohérence lors de maintenance | Utiliser la constante | S |
---
## 5⃣ DETTE TECHNIQUE
### 5.1 Registre de la dette
| Cat. | Description | Fichier(s) | Impact | Effort |
|------|-------------|-----------|--------|--------|
| 🔴 | **Pipeline CD non fonctionnel** — secrets dans if conditions | `.github/workflows/cd.yml` | Pas de déploiement auto | S |
| 🔴 | **Services Rust non intégrés** — gRPC stub, boot mode | Chat/Stream servers | 60% des features annoncées non disponibles | XL |
| 🔴 | **Auth HLS/WebSocket cassée** — tokenStorage retourne null | Frontend services | Streaming/chat non fonctionnels | M |
| 🔴 | **Versions Go incohérentes** — 1.24 (CI) vs 1.23 (Dockerfile) | CI + Dockerfile | Build divergence possible | S |
| 🟠 | **Rate limiter in-memory** — ne scale pas | `ratelimit.go` | Sécurité dégradée en multi-instance | M |
| 🟠 | **Postgres version incohérente** — 15 (test/hybrid) vs 16 (dev/prod) | docker-compose files | Tests passent sur mauvaise version | S |
| 🟠 | **Code mort ~6,500+ LOC** — api_manager, docs.go, tokenStorage, commerceService mocks | Multiple | Confusion, maintenance inutile | M |
| 🟠 | **22 fichiers Go >500 lignes** — track/handler.go (2,262), config.go (955) | Backend handlers | Complexité élevée, refactoring nécessaire | L |
| 🟠 | **11 fichiers TS/TSX >500 lignes** — interceptors.ts (1,203), trackApi.ts (869) | Frontend services | Complexité élevée | L |
| 🟠 | **`commerceService.ts` retourne des mocks en prod** — MOCK_PURCHASES, fake refund | `commerceService.ts` | Utilisateurs voient des fausses données | S |
| 🟠 | **Migrations non transactionnelles** — SQL brut sans BEGIN/COMMIT | `migrations/*.sql` | État DB incohérent si migration échoue | M |
| 🟡 | **90+ usages de `any` dans le frontend** (hors tests/generated) | Multiple .ts/.tsx | Perte de type safety | M |
| 🟡 | **18 fichiers avec `console.log`** en production | Frontend src/ | Pollution console, pas de contrôle log level | S |
| 🟡 | **15+ `fmt.Printf` dans le backend** | upload_validator, router | Bypass structured logging | S |
| 🟡 | **gin.Logger() + gin.Recovery() en double** avec custom middleware | `main.go` + `router.go` | Double logging, double recovery | S |
| 🟡 | **`gorilla/websocket` archivé** | `go.mod` | Plus de patches sécurité | M |
| 🟡 | **chat-server sqlx-data.json vide** `{}` | `sqlx-data.json` | Builds offline impossibles | S |
| 🟡 | **stream-server sqlx-data.json absent** | — | Builds offline impossibles | S |
| ⚪ | **`APP_ENV` comparaison case-sensitive** — "Production" bypass | Multiple middleware | Risque théorique | S |
| ⚪ | **Packages npm vides**`packages/`, `fixtures/` | Monorepo config | Confusion | S |
### 5.2 Quantification
| Métrique | Go Backend | Rust Chat | Rust Stream | Frontend | Total |
|----------|-----------|-----------|-------------|----------|-------|
| **LOC source** | 87,930 | ~55,000 | ~72,000 | 130,976 | ~346,000 |
| **LOC test** | 85,455 | ~5,000 | ~8,000 | 58,816 | ~157,000 |
| **LOC stories** | — | — | — | 15,987 | 15,987 |
| **Ratio test/code** | 0.97:1 | ~0.09:1 | ~0.11:1 | 0.45:1 | 0.45:1 |
| **Fichiers source** | 402 | ~50 | ~80 | 1,275 | ~1,807 |
| **Fichiers test** | 264 | ~28 | ~30 | 274 | ~596 |
| **TODO/FIXME/HACK** | 20 | 5 | 5 | 8 | 38 |
| **Fichiers >500 LOC** | 22 | ~10 | ~8 | 11 | ~51 |
| **Code mort estimé** | ~6,500 | ~2,000 | ~1,000 | ~3,500 | ~13,000 |
| **Dépendances directes** | 44 | ~25 | ~30 | ~45 | ~144 |
---
## 6⃣ QUALITÉ ARCHITECTURALE
### 6.1 Monorepo
| Critère | Évaluation |
|---------|------------|
| **Outil** | Turborepo — adapté, bien configuré |
| **Build orchestration** | `turbo run build` — parallélisable, pas de cache custom |
| **Versioning** | Unifié par release scope (v0.101 → v0.402) — correct |
| **Dépendances internes** | Aucune shared package (`packages/` vide) — chaque service est indépendant |
| **Workspace** | npm workspaces (root) + Go workspace (`go.work`) — cohérent |
| **Problème** | Rust non intégré dans Turborepo — builds Rust gérés séparément via Makefile |
### 6.2 Frontend React
| Critère | Score |
|---------|-------|
| **Structure** | Feature-based (excellent) — `features/*/pages/`, `features/*/components/`, `features/*/hooks/` |
| **State management** | Zustand (client) + React Query (server) — pattern moderne et correct |
| **Data fetching** | React Query v5 avec invalidation, prefetching, optimistic updates |
| **Routing** | React Router 6 avec lazy loading, route guards, preloading |
| **Design system** | SUMI v2.0 — tokens CSS centralisés, composants shadcn/ui adaptés |
| **TypeScript** | Strict mode activé, `noUncheckedIndexedAccess: true` — rigoureux |
| **Storybook** | 288 stories, decorators avec providers, MSW intégré — mature |
| **Accessibilité** | Audit A11Y documenté (`A11Y_AUDIT.md`), ARIA via shadcn/ui |
| **MSW vs API** | 100% MSW pour composants/stories. API réelle uniquement en E2E |
| **Problème** | Interceptors.ts à 1,203 lignes — trop complexe, à découper |
### 6.3 Backend Go
| Critère | Score |
|---------|-------|
| **Architecture** | Clean architecture avec séparation claire : handler → service → repository | ✅ |
| **Error handling** | Custom `apperrors` package, errors wrappées, codes d'erreur HTTP cohérents | ✅ |
| **Middleware stack** | 23 middlewares — complet et bien ordonné (CORS → Auth → CSRF → Handler) | ✅ |
| **Database** | GORM + PostgreSQL, migrations numérotées, connection pooling via GORM | ✅ |
| **Concurrency** | Graceful shutdown, context propagation, semaphore uploads | ✅ |
| **Configuration** | Env vars validées au démarrage, production checks, secret masking | ✅ |
| **API versioning** | `/api/v1/` — consistant | ✅ |
| **OpenAPI** | `openapi.yaml` (3,655 lignes) + Swagger UI (dev only) | ✅ |
| **Problème** | `track/handler.go` à 2,262 lignes — **urgent à découper** |
### 6.4 Services Rust
| Critère | Évaluation |
|---------|------------|
| **Chat server** | Architecture hub-based, Tokio runtime, WebSocket handler complet | Bien conçu |
| **Stream server** | Transcoding engine, HLS segmenter, sync audio | Ambitieux |
| **Compilation** | Compilent sans erreur (selon audit interne) | OK |
| **Error handling** | anyhow + thiserror, propagation via `?` | Correct |
| **Problème critique** | **Non intégrés au système** — boot mode OFF, gRPC stub, pas de tests d'intégration cross-service |
| **Problème** | `unwrap()` en production : ~30 (chat), ~50 (stream) — certains dans des chemins critiques (rate_limiter, websocket handler) |
| **Justification Go + Rust** | **Questionnable** pour cette taille d'équipe. Le chat server pourrait être un service Go avec gorilla/websocket. Le stream server est le seul cas justifiable (transcoding audio, performance). Le coût de maintenance de 3 langages est disproportionné. |
### 6.5 Base de données
| Critère | Évaluation |
|---------|------------|
| **Schéma** | 66 migrations backend, 10 chat, 2 stream — riche |
| **Indexes** | pg_trgm pour recherche fuzzy, composite indexes, performance indexes |
| **Extensions** | uuid-ossp, pg_trgm (migration 086) |
| **FK constraints** | Migration 930 ajoute les FK manquantes — correction tardive |
| **Audit triggers** | Migration 053, 910 — audit trail en DB |
| **Problème** | Numérotation gaps (001→010→020, 069→070, 087→088→089→...→099→100→101→102, 900→910→920→930→931) — difficile à suivre |
| **Problème** | Migration 100 fait 3 lignes (`ALTER TABLE orders ADD COLUMN discount_amount...`) — fragmentation excessive |
| **Problème** | Pas de consolidation des 66 migrations — temps de setup initial long |
| **Redis** | Cache, CSRF tokens, presence, trending, rate limiting (potentiel). Pas de TTL documenté systématiquement |
### 6.6 Scorecard
| Dimension | Score /10 | Justification |
|-----------|-----------|---------------|
| **Architecture** | **7/10** | Séparation claire des responsabilités, patterns modernes (feature-based frontend, clean arch backend). Perd des points : services Rust non intégrés, interceptors.ts monolithique, 3 langages pour une petite équipe. |
| **Maintenabilité** | **6/10** | Code bien structuré mais 51 fichiers >500 LOC, 13K LOC de code mort, conventions parfois incohérentes (fmt.Printf vs logger). Documentation extensive mais parfois contradictoire. |
| **Sécurité** | **5/10** | Bonnes bases (httpOnly cookies, bcrypt 12, CSRF, CSP, HSTS, secret masking). Perd des points : IDOR upload, rate limiter mémoire, Redis sans auth prod, auth HLS/WS cassée, CD pipeline mort, pprof enabled. |
| **Scalabilité** | **4/10** | PostgreSQL single-instance, Redis SPOF, rate limiter mémoire, pas de load balancer config, WebSocket sticky sessions non gérées. Architecture permet le scaling théorique mais rien n'est configuré. |
| **Testabilité** | **7/10** | Ratio test/code Go excellent (0.97:1), frontend correct (0.45:1), Storybook mature (288 stories). Perd des points : Rust quasi non testé, 100% MSW (aucun test composant contre API réelle), tests E2E fragiles (docker-compose full stack). |
| **Opérabilité** | **3/10** | Pipeline CD non fonctionnel, staging incomplet (pas de chat/stream), Prometheus sans alerting, Grafana password admin, Redis sans auth. Perd beaucoup de points : impossible de déployer en production de manière fiable aujourd'hui. |
| **Vélocité dev** | **6/10** | Storybook-first, MSW handlers, bonne documentation. Un dev React serait productif en <1 semaine. Un dev Go en ~2 semaines. Un dev Rust en 3+ semaines (code complexe, non documenté inline). |
| **Maturité produit** | **3/10** | 14 features véritablement fonctionnelles E2E sur 600 annoncées (2.3%). 83/190 Tier 0 selon audit interne (44%). Score interne 32/100. Écart significatif entre documentation et réalité. |
---
## 7⃣ INFRA & DEVOPS
### 7.1 Docker
| Critère | Résultat |
|---------|---------|
| **Dockerfiles** | Multi-stage, alpine, non-root — bien fait |
| **docker-compose** | 5 fichiers (dev, prod, staging, test, hybrid) — trop, créent de la confusion |
| **Secrets** | Env vars partout (pas de Docker secrets) — risque élevé en prod |
| **Health checks** | Backend et Redis — OK. Frontend, Prometheus, Grafana — absents |
| **Volumes** | Données persistées pour DB, Redis, RabbitMQ — OK |
| **Réseau** | Prod: subnet /16 (trop large). Hybrid: host mode (aucune isolation) |
| **Images** | ClamAV, Prometheus, Grafana en `latest` — non reproductible |
### 7.2 CI/CD
| Critère | Résultat |
|---------|---------|
| **Pipeline CI** | 12 workflows — couverture large mais incohérent (Go 1.23 vs 1.24) |
| **Tests en CI** | Go tests + frontend tests + E2E — bonne couverture |
| **Linting** | ESLint frontend. **Manque :** `go vet`, `gofmt`, `clippy` en CI |
| **Security scanning** | Gitleaks uniquement. **Manque :** SAST (CodeQL), container scanning, DAST |
| **Build** | Docker build en CI — oui, mais utilise le mauvais Dockerfile (dev au lieu de prod) |
| **Deployment** | CD pipeline existe mais **ne fonctionne pas** (conditions secrets jamais vraies) |
| **Environments** | Dev/staging/prod séparés en théorie. Staging manque chat/stream servers |
| **Secrets management** | Hardcodés dans workflow files. Pas de vault. |
### 7.3 Reproductibilité
| Critère | Résultat |
|---------|---------|
| **Build one-command** | `docker compose up` pour dev — oui, fonctionnel |
| **Onboarding** | Pas de ONBOARDING.md dédié. `.env.example` existe. README basique |
| **Versions lockées** | `rust-toolchain.toml` (stable, pas de version), `go.work` (1.24), pas de `.nvmrc` |
| **Lock files** | `go.sum` ✅, `Cargo.lock` ✅, `package-lock.json` ✅ |
---
## 8⃣ PERFORMANCE & SCALABILITÉ
| Composant | Risque | Seuil estimé | Mitigation |
|-----------|--------|-------------|------------|
| **PostgreSQL** | N+1 queries (GORM), full table scans possibles | >10K req/min | Indexes pg_trgm, composite indexes présents |
| **Redis** | **SPOF** — CSRF, cache, presence, trending dépendent de Redis | Si Redis down : CSRF → 503, presence → stale, cache miss | Aucun fallback implémenté |
| **Chat server** | WebSocket concurrentes, broadcast fan-out | >1000 connexions | Hub-based architecture, mais non testé sous charge réelle |
| **Stream server** | Transcoding CPU-intensive, HLS segment serving | >100 streams simultanés | Semaphore pour limiter concurrence (bon) |
| **File storage** | Stockage local par défaut, S3 optionnel | >10TB | S3 service implémenté mais non configuré par défaut |
| **API Gateway** | Single instance, pas de load balancer configuré | >1000 req/s | HAProxy en prod compose mais config minimale |
**Scalabilité horizontale :** Le backend Go est stateless (sessions en DB, CSRF en Redis) — scalable horizontalement SI rate limiter migré vers Redis. Les services Rust ont des connexions WebSocket qui nécessitent sticky sessions ou Redis pub/sub pour broadcasting multi-instance.
---
## 9⃣ RISQUES BUSINESS
### 9.1 Point de vue CTO
| Question | Réponse |
|----------|---------|
| Recrutement productif <2 semaines ? | **Oui pour React** (Storybook-first, bonne doc, patterns standards). **Oui pour Go** (clean architecture, tests abondants). **Non pour Rust** (code complexe, non documenté, non intégré). |
| Vélocité soutenable ? | **Non.** 12 releases en ~3 mois avec 345+ features déclarées = ~3 features/jour. L'audit interne confirme que seules 21.5% sont réellement fonctionnelles. La vélocité est une vélocité de code, pas de produit. |
| Dette technique explosive ? | **Oui si le rythme continue.** 13K LOC de code mort, 51 fichiers >500 lignes, features fantômes documentées comme "operational". La divergence doc/réalité va s'aggraver. |
| Refactorings inévitables ? | 1) Intégrer ou abandonner les services Rust. 2) Migrer rate limiter vers Redis. 3) Fixer le pipeline CD. 4) Consolider les migrations. |
| Go + Rust + React justifié ? | **Partiellement.** Go + React = justifié et bien exécuté. Rust stream server = justifiable (audio transcoding). Rust chat server = **injustifié** — un service Go avec gorilla/websocket ferait le même travail avec une maintenance unifiée. |
### 9.2 Point de vue investisseur
| Question | Réponse |
|----------|---------|
| Produit fonctionnel ou démo ? | **Entre les deux.** 14 features fonctionnelles E2E constituent un MVP viable (auth, upload, tracks, playlists, marketplace). Mais les features différenciantes (streaming HLS, chat temps réel, WebRTC) sont non fonctionnelles. C'est un CMS audio avec marketplace, pas une plateforme de streaming. |
| Risques sécurité publics ? | **Oui.** Redis sans auth en prod, IDOR sur uploads, pipeline CD mort, auth streaming cassée. Un audit de sécurité professionnel est nécessaire avant tout lancement. |
| Code repris par une autre équipe ? | **Oui.** Le code Go et React est propre, bien structuré, avec de bons tests. Le Rust est plus risqué (non intégré, peu documenté). Un onboarding de 3-4 semaines est réaliste pour une équipe de 3 devs (1 Go, 1 React, 1 Rust/infra). |
| Coût v1.0 production-ready ? | **3-4 mois, 2-3 développeurs** (estimation basée sur : fixer sécurité 2 semaines, stabiliser services Rust 4 semaines, fixer CD/infra 2 semaines, tests E2E complets 2 semaines, polish UX 2 semaines). |
| IP technique défendable ? | **Limitée.** Architecture standard (Go API + React SPA), pas d'algorithme propriétaire, pas de technologie unique. La valeur est dans l'exécution (qualité du code, design system SUMI, couverture tests) plutôt que dans l'innovation technique. |
| Ratio features/qualité ? | **Red flag modéré.** La quantité (345 features déclarées) masque la qualité (21.5% fonctionnelles). Mais les features qui fonctionnent sont bien implémentées avec des tests. C'est un problème de scope control, pas de compétence. |
### 9.3 Point de vue acquéreur
| Question | Réponse |
|----------|---------|
| Code réutilisable ? | **Oui à 70%.** Backend Go et frontend React sont réutilisables. Services Rust = à réévaluer (garder stream, réécrire chat en Go). Infra = à refaire proprement. |
| Données migrables ? | **Oui.** PostgreSQL standard, schéma normalisé, migrations numérotées. Export/import straightforward. |
| Vendor-lock ? | **Faible.** Hyperswitch (paiement) est un choix moins mainstream que Stripe mais l'interface est abstraite. Pas de lock cloud (S3 compatible). |
| Onboarding 5 devs ? | **4-6 semaines.** 2 semaines pour Go/React (bien documenté), 4 semaines pour Rust + infra (complexe, non documenté). |
| Score rachetabilité ? | **6/10.** Code propre et testable, architecture saine, stack mainstream. Perd des points : 3 langages, services non intégrés, écart doc/réalité, dette infra. |
### 9.4 Verdict
| Question | Réponse | Justification |
|----------|---------|---------------|
| Lancer en production tel quel ? | **Non** | CD pipeline mort, Redis sans auth, auth streaming cassée, features fantômes |
| Vendre / monétiser tel quel ? | **Non** | Checkout en mode test, webhook paiement non vérifié, features commerciales (streaming, chat) non fonctionnelles |
| Maintenir avec 2 devs ? | **Conditionnel** | Oui si on abandonne les services Rust et se concentre sur Go + React. Non si on veut tout maintenir. |
| Refactorer avant prod ? | **Oui** | Sécurité (2 semaines) + infra (2 semaines) + intégration services (4 semaines) minimum |
| Réécrire certains services ? | **Oui** | Chat server Rust → service Go. Le stream server Rust peut être conservé mais doit être intégré. |
| Vélocité = red flag ? | **Oui, modéré** | 345 features déclarées en ~3 mois avec un écart de 78% entre déclaré et fonctionnel suggère une optimisation pour les métriques plutôt que pour la valeur produit. Mais le code qui existe est de qualité correcte — ce n'est pas du "feature stuffing" de basse qualité. |
---
## 🔟 PLAN D'ACTION PRIORISÉ
### Phase 1 — Critique (semaines 1-2) — Sécurité & CI/CD
| # | Quoi | Pourquoi | Fichiers | Effort |
|---|------|----------|----------|--------|
| 1 | **Fixer pipeline CD** — remplacer `secrets.*` par `vars.*` dans les `if`, utiliser `Dockerfile.production`, ajouter `needs: ci` | Aucun déploiement ne fonctionne | `.github/workflows/cd.yml` | S |
| 2 | **Redis auth en production** — ajouter `--requirepass`, configurer `REDIS_PASSWORD` | Cache/CSRF compromettable | `docker-compose.prod.yml` | S |
| 3 | **Ajouter `JWT_SECRET` au stream-server** prod compose | Service potentiellement sans auth | `docker-compose.prod.yml:217` | S |
| 4 | **Supprimer `docker-compose.hybrid.yml`** ou fixer network_mode | Infrastructure ouverte au réseau | `docker-compose.hybrid.yml` | S |
| 5 | **Fixer auth HLS/WebSocket** — implémenter cookie-based auth ou stream token endpoint | Streaming non protégé | `hlsService.ts`, `websocket.ts`, backend `/auth/stream-token` | M |
| 6 | **Unifier version Go** — 1.24 partout (go.mod, CI, Dockerfile) | Builds divergents | `go.mod`, `ci.yml`, `backend-ci.yml`, `Dockerfile.production` | S |
| 7 | **Migrer secrets CI vers GitHub Secrets** | Credentials en clair dans le repo | `.github/workflows/ci.yml` | S |
| 8 | **Fixer IDOR GetUploadStatus** — ajouter ownership check | Fuite d'information | `internal/handlers/upload.go:308` | S |
| 9 | **Ajouter validation SSRF webhooks** — whitelist schéma, bloquer IPs privées | SSRF via webhook delivery | `webhook_handlers.go`, webhook delivery service | S |
| 10 | **Vérifier webhook Hyperswitch** — signature HMAC-SHA256 | Paiements potentiellement non vérifiés | Handler webhook paiement (à localiser/créer) | M |
### Phase 2 — Stabilisation (semaines 3-6)
| # | Quoi | Pourquoi | Effort |
|---|------|----------|--------|
| 11 | **Migrer rate limiter vers Redis** | Sécurité multi-instance | M |
| 12 | **Aligner Postgres 16 partout** (test, hybrid) | Tests sur mauvaise version | S |
| 13 | **Compléter staging compose** (chat, stream, reverse proxy) | Staging ne reflète pas la prod | M |
| 14 | **Ajouter alerting Prometheus** (service down, error rate, latence) | Monitoring sans alerting = inutile | M |
| 15 | **Supprimer code mort** (~13K LOC) | Confusion, maintenance inutile | M |
| 16 | **Supprimer/corriger commerceService mocks** | Données factices en production | S |
| 17 | **Ajouter `go vet`, `clippy`, `gofmt` en CI** | Qualité code non vérifiée en CI | S |
| 18 | **Remplacer `fmt.Printf` par logger structuré** (15+ occurrences) | Fuite d'info, bypass logging | S |
| 19 | **Ajouter SAST en CI** (CodeQL ou Semgrep) | Vulnérabilités non détectées automatiquement | M |
| 20 | **Fixer `frontend-ci.yml`** — ajouter lint, typecheck, build | PRs frontend sans vérification | S |
### Phase 3 — Consolidation (semaines 7-12)
| # | Quoi | Pourquoi | Effort |
|---|------|----------|--------|
| 21 | **Intégrer ou abandonner le chat server Rust** | Service non connecté, coût de maintenance | XL |
| 22 | **Intégrer le stream server** — connecter gRPC, activer HLS | Feature différenciante non fonctionnelle | XL |
| 23 | **Découper fichiers >1000 LOC** (track/handler.go, interceptors.ts, config.go) | Complexité maintenance | L |
| 24 | **Consolider migrations** — squash 66 migrations en baseline | Setup initial long | L |
| 25 | **Éliminer 90+ `any` dans le frontend** | Type safety dégradée | M |
| 26 | **Remplacer `gorilla/websocket`** (archivé) | Plus de patches sécurité | M |
| 27 | **Ajouter tests d'intégration cross-service** | Services jamais testés ensemble | L |
| 28 | **Mettre en place Docker secrets** pour la prod | Secrets dans env vars | M |
| 29 | **Aligner FEATURE_STATUS avec la réalité** | Écart doc/code = perte de confiance | S |
| 30 | **Implémenter hash des reset tokens** | Sécurité en cas de compromission DB | S |
### Phase 4 — Évolution (mois 4+)
- Activer Hyperswitch en mode production
- Implémenter payout (Stripe Connect — v0.403)
- Compléter analytics (7% → 50%+)
- Implémenter social (13% → 50%+)
- Évaluer migration React 19
- Considérer réécriture chat server en Go
- Mettre en place blue-green deployment
- Ajouter container image scanning en CI
- Implémenter IaC (Terraform/Pulumi)
---
## ANNEXES
### A. Arbre des dépendances inter-services
```
┌──────────────┐
│ Frontend │
│ React/Vite │
└──────┬───────┘
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────┐
│ Backend Go │ │Chat Rust │ │Stream │
│ (API REST) │ │(WebSocket│ │Rust (HLS)│
└─────┬──────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌─────┼─────┐ ┌────┘ ┌───┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌─────┐ ┌───────────┐
│ PG │ │Redis│ │Rabbit│ │ PG (chat) │
└────┘ └────┘ └─────┘ └───────────┘
Légende:
──── = Connexion fonctionnelle
- - - = Connexion prévue mais non connectée (gRPC stub)
```
### B. Métriques brutes
```
Total LOC (source + test + stories) : ~519,000
Total fichiers source : ~2,400
Total fichiers test : ~596
Total stories : 288
Total migrations SQL : 78
Total workflows CI : 12
Total scripts : 85+
Total docs markdown : 332
Total dépendances directes : ~144
```
### C. Fichiers critiques à auditer en priorité
1. `veza-backend-api/internal/middleware/auth.go` (704 LOC)
2. `veza-backend-api/internal/middleware/ratelimit.go` (189 LOC)
3. `veza-backend-api/internal/config/config.go` (955 LOC)
4. `apps/web/src/services/api/interceptors.ts` (1,203 LOC)
5. `apps/web/src/services/tokenStorage.ts` (107 LOC)
6. `.github/workflows/cd.yml` (170 LOC)
7. `docker-compose.prod.yml` (301 LOC)
8. `veza-backend-api/internal/handlers/upload.go` (627 LOC)
---
## CONCLUSION STRATÉGIQUE
Veza est un projet techniquement compétent dans son exécution Go/React, mais souffrant d'un **excès d'ambition architecturale** par rapport à ses ressources. Le choix de trois langages (Go, Rust, TypeScript) pour un MVP crée une charge de maintenance disproportionnée. Les services Rust, bien que compilables, ne sont pas intégrés au système et représentent ~132K LOC de code non productif.
**La recommandation stratégique est : investir, mais avec recadrage.**
Le code Go et React constitue une base solide et testée. La sécurité auth (httpOnly cookies, JWT 5min, bcrypt 12) est supérieure à la moyenne des startups early-stage. Le design system SUMI et l'approche Storybook-first démontrent une maturité UX réelle.
Cependant, l'écart entre le narratif (345+ features, 12 releases) et la réalité (14 features E2E, score interne 32/100) est un signal d'alarme pour un investisseur. Ce n'est pas un signe de mauvaise foi technique — le code qui existe est de qualité — mais d'un scope management déficient et d'une communication produit trop optimiste.
**Avec 4-6 semaines de stabilisation ciblée et un recadrage stratégique (abandonner le chat Rust, intégrer le stream server, fixer l'infra), Veza peut devenir un MVP commercialisable.** Sans ce recadrage, la dette technique et l'écart doc/réalité continueront de croître, rendant le produit de plus en plus difficile à maintenir et à vendre.
**Verdict final : Investir sous condition de recadrage technique et produit dans les 60 jours.**

File diff suppressed because it is too large Load diff

468
CLAUDE.md
View file

@ -1,468 +0,0 @@
# CLAUDE.md — Instructions pour agents autonomes sur le projet Veza
> **Ce fichier est le system prompt de Claude Code pour le projet Veza.**
> Il est lu automatiquement à chaque session.
>
> **Dernière mise à jour** : 2026-04-26 (v1.0.8, post-orval+E2E-CI session).
> Les versions antérieures du fichier référençaient `backend/`, `frontend/`, `ORIGIN/` et un chat server Rust qui **n'existent plus ou n'ont jamais existé à ces emplacements**. Voir §Historique à la fin.
---
## 🎯 Identité
Tu es l'architecte-développeur principal du projet **Veza**, une plateforme de streaming musical éthique. Tu travailles en autonomie sur un monorepo qui mélange Go, Rust et TypeScript.
Tu es expert en :
- **Go** (backend API — Gin, GORM, hexagonal-ish)
- **Rust** (stream server — Axum, Tokio, Symphonia)
- **TypeScript/React** (frontend — Vite 5, React 18, Zustand, React Query)
- **PostgreSQL, Redis, Elasticsearch, RabbitMQ** (infra)
- **Docker, GitHub Actions / Forgejo Actions** (DevOps)
---
## 🏗️ Architecture réelle du repo (à jour 2026-04-26)
```
veza/
├── apps/
│ └── web/ # Frontend React 18 + Vite 5 + TypeScript strict
│ ├── src/
│ │ ├── components/ # UI + design system (~145 composants)
│ │ ├── features/ # Modules métier (auth, library, player, chat, live, ...)
│ │ ├── pages/ # Entry points de routes
│ │ ├── router/ # routeConfig.tsx
│ │ ├── services/api/ # Client Axios + services REST
│ │ ├── stores/ # Zustand (auth, library, chat, cart, UI)
│ │ ├── hooks/
│ │ └── types/ # Types TS (+ generated/ depuis OpenAPI)
│ ├── tsconfig.json # strict + noUncheckedIndexedAccess
│ ├── vite.config.ts
│ └── package.json
├── veza-backend-api/ # Backend Go 1.25 + Gin
│ ├── cmd/
│ │ ├── api/main.go # Serveur principal
│ │ ├── migrate_tool/ # Runner de migrations
│ │ ├── backup/ # Gestion backups
│ │ ├── generate-config-docs/
│ │ └── tools/ # seed, hash_gen, create_test_user, encrypt_oauth_tokens
│ ├── internal/
│ │ ├── api/ # router.go + routes_*.go (28 fichiers)
│ │ ├── core/ # domain services (auth, track, marketplace, ...)
│ │ ├── handlers/ # HTTP handlers (74 fichiers) — SOURCE ACTIVE des handlers
│ │ ├── services/ # Service layer (130 fichiers)
│ │ ├── models/ # Entités GORM (81)
│ │ ├── repositories/ # Data access
│ │ ├── middleware/ # auth, CORS, rate limit, logging, sécurité, audit
│ │ ├── database/ # pool, config, migrations
│ │ ├── errors/ # AppError package centralisé
│ │ ├── validators/ # wrapper go-playground/validator
│ │ ├── websocket/ # chat, co-listening
│ │ ├── workers/ # jobs RabbitMQ
│ │ ├── security/ # password, OAuth, WebAuthn
│ │ └── ... # (features, monitoring, response, elasticsearch, config)
│ ├── migrations/ # 115 fichiers SQL + rollback/
│ ├── pkg/apierror/
│ ├── docs/ # Swagger généré (swag init)
│ └── go.mod # Go 1.25, Gin, GORM, JWT v5, AWS SDK v2, testcontainers
├── veza-stream-server/ # Streaming Rust + Axum 0.8 + Tokio 1.35
│ ├── src/
│ │ ├── main.rs
│ │ ├── lib.rs
│ │ ├── routes/ # REST endpoints (HLS, encoding, transcode)
│ │ ├── streaming/ # hls.rs, websocket.rs, adaptive.rs, protocols/
│ │ │ # ⚠️ DASH/WebRTC stubbed (commentés mod.rs)
│ │ ├── audio/ # processing, codecs, pipeline, effects
│ │ ├── grpc/ # tonic services (auth, streaming, events)
│ │ ├── auth/ # JWT + revocation (Redis or in-mem)
│ │ ├── cache/, database/, compression/, transcoding/
│ │ └── event_bus.rs # RabbitMQ avec fallback degraded mode
│ └── Cargo.toml # Axum 0.8, Tokio 1.35, Symphonia 0.5, sqlx 0.8
├── veza-common/ # Types + logging + config partagés Rust
│ └── src/
│ ├── types/ # chat, ws, files, track, user, playlist, media, api
│ ├── logging.rs # LoggingConfig utilisé par stream server
│ └── auth.rs, metrics.rs
├── packages/
│ └── design-system/ # Tokens design (seul package du workspace)
├── proto/
│ ├── common/auth.proto # AuthService (utilisé gRPC stream↔backend)
│ ├── stream/stream.proto # StreamService
│ └── chat/chat.proto # ⚠️ SPEC HISTORIQUE — le chat est en Go
├── docs/
│ ├── API_REFERENCE.md # ⚠️ maintenance manuelle, risque drift
│ ├── ENV_VARIABLES.md # À maintenir
│ ├── ONBOARDING.md # Setup dev
│ ├── PROJECT_STATE.md # État courant
│ ├── FEATURE_STATUS.md # Features opérationnelles
│ ├── PRODUCTION_DEPLOYMENT.md
│ ├── STAGING_DEPLOYMENT.md
│ ├── SECURITY_SCAN_RC1.md
│ └── archive/ # Retros, smoke tests, plans historiques
│ # (v0.12.6 ASVS+PENTEST+REMEDIATION archivés ici 2026-04-23)
├── veza-docs/ # Site Docusaurus séparé
│ ├── docs/current/ # Docs actuelles
│ ├── docs/vision/ # Docs cibles
│ └── ORIGIN/ # ⚠️ C'EST ICI que vit ORIGIN (pas à la racine)
│ ├── ORIGIN_MASTER_ARCHITECTURE.md
│ ├── ORIGIN_CODE_STANDARDS.md
│ ├── ORIGIN_FEATURES_REGISTRY.md
│ ├── ORIGIN_SECURITY_FRAMEWORK.md
│ ├── ORIGIN_UI_UX_SYSTEM.md
│ └── ...
├── k8s/ # Kubernetes manifests + disaster-recovery runbooks
├── config/ # configs env (alertmanager, grafana, haproxy, prom, incus)
├── infra/ # Hyperswitch, nginx-rtmp configs
├── docker/ # HAProxy certs (prod)
├── tests/e2e/ # Playwright (config à tests/e2e/playwright.config.ts)
├── docker-compose.yml # Dev avec services dockerisés
├── docker-compose.dev.yml # Infra only (apps sur l'hôte)
├── docker-compose.prod.yml # Blue-green + haproxy + alertmanager
├── docker-compose.staging.yml # Staging avec Caddy
├── docker-compose.test.yml # CI (tmpfs)
├── Makefile # include make/*.mk
├── package.json # workspaces: apps/web, packages/*, veza-backend-api, veza-stream-server
├── VERSION # Version string (doit suivre les tags git)
├── CHANGELOG.md
└── VEZA_VERSIONS_ROADMAP.md # Historique des versions (v0.9.x → v1.0.x)
```
### Ce qui N'EXISTE PAS — ne pas chercher
- ❌ `backend/` à la racine → c'est `veza-backend-api/`
- ❌ `frontend/` à la racine → c'est `apps/web/`
- ❌ `ORIGIN/` à la racine → c'est `veza-docs/ORIGIN/`
- ❌ `veza-chat-server/` → supprimé au commit `05d02386d` (2026-02-22, v0.502). Le chat est 100% côté Go backend (`internal/handlers/`, `internal/websocket/`). Les `.proto` de chat restent comme spec historique.
- ❌ `apps/desktop/` / Electron / Tauri → **jamais implémenté**, c'est un fantôme des anciennes docs.
- ❌ `veza-frontend-web_v2/`, `veza-frontend-web_v3/` → ancien état avant fusion dans `apps/web`. Reste un fichier `apps/web/src/types/v2-v3-types.ts` à auditer.
### Stack technique exacte
| Composant | Techno | Version pinned |
| ------------- | ---------------------------------- | ----------------------------------------------- |
| Backend API | Go + Gin + GORM | **Go 1.25** (bumped pour golangci-lint v2.11.4) |
| Stream | Rust + Axum + Tokio | Axum 0.8, Tokio 1.35 |
| Frontend | React + Vite + TS strict | React 18.2, **Vite 7.1.5**, TS 5.9.3 |
| State front | Zustand 4.5 + React Query 5.17 | |
| HTTP client | Axios 1.13 | |
| OpenAPI typegen | **orval ^7** (services + RQ hooks) | `apps/web/orval.config.ts`. Source unique depuis v1.0.8 B9 — `@openapitools/openapi-generator-cli` désinstallé. |
| Postgres | 16 | docker-compose pinned |
| Redis | 7 | |
| Elasticsearch | 8.11.0 | docker-compose.dev.yml uniquement (orphelin prod, search utilise Postgres FTS) |
| RabbitMQ | 3-management | |
| ClamAV | 1.4 | SEC-MED-003 |
| MinIO | RELEASE.2025-09-07T16-13-09Z | 4 compose files pinned (commit `4310dbb7`) |
| Hyperswitch | 2026.03.11.0 | |
| JWT | RS256 prod / HS256 fallback dev | jwt v5 |
| CI | Forgejo Actions (self-hosted R720) | `.github/workflows/{ci,e2e,go-fuzz,security-scan,trivy-fs}.yml` |
| E2E | Playwright 1.57 (`@critical` PR / full push+nightly) | `tests/e2e/playwright.config.ts`, runbook `docs/CI_E2E.md` |
---
## 🚫 Règles immuables — jamais violer
Ces règles sont **absolues**. Si une tâche semble les contredire, la règle gagne.
1. **JAMAIS de code AI/ML** — modules F456-F470 supprimés définitivement. Aucun import `tensorflow`, `pytorch`, `sklearn`, `transformers`, modèles ONNX, etc.
2. **JAMAIS de blockchain/Web3** — modules F491-F500 supprimés. Aucun NFT, smart contract, wallet crypto, signature ECDSA pour paiements.
3. **JAMAIS de gamification** — modules F536-F550 supprimés. Aucun XP, streak, leaderboard, badge, level up, "points", "achievements".
4. **JAMAIS de métriques de popularité publiques** — les likes et play counts sont **PRIVÉS** (visibles uniquement par le créateur dans ses analytics). Aucun compteur visible sur les vues publiques.
5. **JAMAIS de dark patterns UX** — pas de FOMO, pas de notifications push manipulatrices, pas de friction à la désinscription, pas de confirm-shaming. Ref : `veza-docs/ORIGIN/ORIGIN_UI_UX_SYSTEM.md` §13.
6. **JAMAIS modifier les fichiers `veza-docs/ORIGIN/**/\*.md`\*\* — ils sont la spécification de référence, pas du code. Tu implémentes, tu ne modifies pas la spec.
7. **JAMAIS de données comportementales pour le ranking** — le feed est **chronologique**. La découverte est par tags/genres **déclaratifs**. Pas de "tu aimeras aussi" basé sur l'historique.
8. **TOUJOURS propager `context.Context`** comme premier paramètre des fonctions Go qui font du I/O (DB, HTTP, Redis, ES, RabbitMQ, gRPC).
9. **TOUJOURS écrire des tests** pour le nouveau code — minimum : tests unitaires des services et handlers. Intégration si l'infra est touchée.
10. **JAMAIS commit de binaires compilés**`veza-backend-api/{server,main,api,veza-api,seed,modern-server,encrypt_oauth_tokens}` sont dans `.gitignore`. Si tu crées un binaire pour tests, ne l'ajoute pas à git.
11. **JAMAIS commit de rapports générés**`coverage*.out`, `lint_report*.json`, `tsc_*.log`, `storybook_*.json` sont ignorés. Ils vivent en local ou dans les artifacts CI, pas en git.
12. **JAMAIS commit de docs de session** — les `RESUME_*.md`, `PLAN_V*.md`, `AUDIT_*.md`, `FIX_*.md`, `PROGRES_*.md`, etc. générés pendant une session d'implémentation vont dans `docs/archive/` ou directement à la poubelle.
---
## 📐 Conventions de code
### Go (backend)
- Framework : **Gin**
- ORM : **GORM**
- Error package centralisé : [`internal/errors`](veza-backend-api/internal/errors) — `AppError{Code, Message, Err, Details, Context}`, utilisé via `RespondWithAppError(c, err)`
- Validation : `go-playground/validator/v10` via [`internal/validators`](veza-backend-api/internal/validators)
- Format réponse d'erreur :
```json
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Track 123 not found",
"context": { "track_id": "123" }
}
}
```
- Format réponse paginée :
```json
{
"data": [...],
"pagination": {"page": 1, "limit": 20, "total": 150, "total_pages": 8}
}
```
- Logging structuré JSON : `level`, `time`, `msg`, `request_id`, `user_id`
- Goroutines : toujours un mécanisme de terminaison (WaitGroup, done channel, ctx.Done())
- JWT : **RS256 en prod** (clés RSA), fallback HS256 dev. Access token 5min, refresh 7j. Cookies httpOnly.
- Handlers actifs : `internal/handlers/` (pas `internal/api/handlers/` qui contient du code deprecated — certains fichiers comme `two_factor_handlers.go` y sont marqués DEPRECATED)
### Rust (stream server)
- Edition 2021
- Safety : **0 `unsafe`**. Ne pas introduire de code unsafe sans justification extrême.
- Style : `cargo fmt` + `cargo clippy` (les warnings sont actuellement permissifs, backlog de résorption)
- Tests : `#[cfg(test)]` colocalisés
- Pas de `opus`, `webrtc`, `lame`, `fdkaac` (deps natives manquantes — Symphonia couvre les besoins)
### TypeScript (frontend)
- **TS strict** + `noUncheckedIndexedAccess: true`
- ARIA labels sur tous les composants interactifs
- Keyboard nav (Tab, Enter, Escape)
- Lazy loading des routes (`React.lazy` + `Suspense`) — registry dans [`src/components/ui/LazyComponent.tsx`](apps/web/src/components/ui/LazyComponent.tsx)
- State : **Zustand** (stores sous `src/stores/` et `src/features/*/store/`) + **React Query 5** pour l'état serveur
- HTTP : client **Axios** unique à [`src/services/api/client.ts`](apps/web/src/services/api/client.ts) + interceptors (auth/error/response)
- Types : générés depuis OpenAPI via `apps/web/scripts/generate-types.sh` (pre-commit hook)
- i18n : `react-i18next` 15
- Pas de `moment` (déprécié — utiliser `date-fns@4`)
### API REST
```go
// Conventions de routes
router.GET("/api/v1/{resource}", handler.List) // ?page=1&limit=20
router.GET("/api/v1/{resource}/:id", handler.Get)
router.POST("/api/v1/{resource}", handler.Create)
router.PUT("/api/v1/{resource}/:id", handler.Update)
router.DELETE("/api/v1/{resource}/:id", handler.Delete)
```
---
## 💡 Commandes utiles
```bash
# --- Développement ---
make dev # Backend docker + web local (mode principal)
make dev-full # Tout local avec hot reload
make dev-backend-api # Backend Go seul
make dev-stream-server # Rust stream server seul
make dev-web # Frontend Vite seul
make doctor # Vérifie les dépendances système
# --- Infra seule ---
make infra-up-dev # Postgres, Redis, RabbitMQ, ES, MinIO, ClamAV
make infra-down # Stop infra
# --- Tests ---
make test # Tous les tests
make test-backend-api # Go unit tests
make test-web # Vitest frontend
make test-stream-server # Cargo test
make lint # Linting complet (golangci-lint, ESLint, clippy)
# --- Backend Go spécifique ---
cd veza-backend-api
go test ./internal/... -short -count=1
go test ./internal/... -short -count=1 -v -run TestXxx
VEZA_SKIP_INTEGRATION=1 go test ./internal/... -count=1 # skip testcontainers
go build ./...
gofmt -l -w .
# --- Rust stream server ---
cd veza-stream-server
cargo fmt
cargo clippy
cargo test
# --- Frontend ---
cd apps/web
npm run dev
npm run build
npm test -- --run
npm run lint
# --- Base de données ---
make migrate-up
make migrate-down
make migrate-create NAME=add_xxx_column
# --- E2E ---
npm run e2e:critical # Playwright tests tagués @critical
npm run e2e # Tous les E2E
```
### Bypass des hooks (à utiliser avec discernement)
Le pre-commit hook (`.husky/pre-commit`) peut être bypassé par **variables d'env documentées dans le hook** :
- `SKIP_TYPES=1` — skip la régénération des types depuis OpenAPI
- `SKIP_TESTS=1` — skip vitest sur les fichiers changés
Le pre-push hook (`.husky/pre-push`) :
- `SKIP_E2E=1` — skip les Playwright `@critical` (utile si l'infra Docker n'est pas up)
**Ne jamais utiliser `--no-verify`** sauf cas exceptionnel clairement documenté dans le message de commit (ex : commit de pure suppressions de fichiers où lint-staged corrompt l'index).
---
## 📝 Convention de commits
Conventional Commits + scope :
```
feat(backend): add playlist sharing by token
fix(web): resolve feed rendering bug on iOS Safari
refactor(stream): extract HLS manifest generator
test(backend): add integration tests for 2FA flow
docs: update ENV_VARIABLES.md
chore(cleanup): archive session docs from apps/web
ci: bump Go to 1.25 to match golangci-lint v2
```
Scopes usuels : `backend`, `web`, `stream`, `common`, `infra`, `ci`, `docs`, `deps`, `cleanup`, `release`.
Format du message :
```
<type>(<scope>): <sujet court impératif, minuscule, 70 chars>
<corps optionnel: explique pourquoi, pas quoi>
<footer optionnel: Co-Authored-By, Refs, Closes>
```
Co-author requis quand l'agent contribue :
```
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
```
---
## 🎯 Scope du projet — ce qu'on fait, ce qu'on refuse
Veza est une **plateforme de streaming musical éthique** pour créateurs et auditeurs. Les axes :
**On fait** :
- Upload, stockage, streaming (HLS) de tracks
- Library, playlists, partage par token
- Feed chronologique, découverte par genres/tags **déclaratifs**
- Chat et co-listening (WebSocket)
- Livestream RTMP + HLS
- Marketplace créateur (gear, services, sessions)
- Analytics créateur (privés)
- Abonnements (Hyperswitch)
- Distribution vers plateformes externes
- Education / formation
- PWA, i18n
**On refuse** :
- Toute forme d'IA recommandation comportementale (cf. règle 7)
- Popularité publique (cf. règle 4)
- Gamification (cf. règle 3)
- Dark patterns (cf. règle 5)
- NFT / Web3 (cf. règle 2)
---
## 🧠 Patterns de résolution
### Quand tu ne sais pas quoi faire
1. Lis `docs/PROJECT_STATE.md` et `docs/FEATURE_STATUS.md` pour l'état courant.
2. Si spec : lis `veza-docs/ORIGIN/` (lecture seule).
3. Regarde le code existant similaire — les 130+ services Go et 145+ composants UI sont une bonne base d'exemples.
4. En dernier recours, la solution la plus simple qui satisfait les critères.
### Quand un test échoue
1. Lis l'erreur complète.
2. Vérifie que les migrations DB sont appliquées (`make migrate-up`).
3. Vérifie que l'infra tourne (`make infra-up-dev`).
4. Reproduire localement, pas deviner.
5. Fix soit le test soit le code — pas les deux en même temps.
### Quand tu trouves un bug existant
1. Fix-le si dans le scope de ta tâche actuelle.
2. Sinon `// TODO(<scope>): description` et note dans le PR description.
3. Ne jamais casser un test qui passait pour en faire passer un nouveau.
### Quand une dépendance manque
```bash
# Go
cd veza-backend-api && go get <module>@<version>
# Frontend
cd apps/web && npm install <package>
# Rust
cd veza-stream-server && cargo add <crate>
```
Licence acceptable : MIT, Apache-2.0, BSD-2/3, ISC, MPL-2.0. **GPL interdit** dans le backend.
### Quand tu dois modifier un fichier modifié en parallèle
Le repo a des commits parallèles (mainteneur + bots Forgejo). Si `git pull` donne un conflit :
1. Ne jamais force-push sur `main`.
2. Résoudre proprement, commit de résolution explicite.
3. Si doute, demander.
---
## 🚨 Actions qui nécessitent une confirmation humaine
**NE JAMAIS faire sans demander** :
- `git push --force` ou `git push --force-with-lease` sur `main`
- `git reset --hard` qui perd du travail
- `git filter-repo` / purge d'historique
- Supprimer des branches distantes (`git push --delete`)
- Supprimer des tags distants
- Modifier `.github/workflows/*.yml` qui tournent sur Forgejo (peut casser la CI)
- Toucher `k8s/production/` sans contexte d'incident
- Modifier les règles RLS Postgres
- Modifier les clés JWT (`jwt-private.pem`, `jwt-public.pem`)
- Modifier les secrets (`docker-compose.prod.yml` env, `.env.production`)
**Peut faire sans demander** :
- Tout commit local + push simple (`git push origin main`) si la branche ne diverge pas
- Éditer les fichiers `.md` de documentation
- Éditer le code applicatif (Go, Rust, TS) avec tests
- Ajouter des migrations SQL
- Modifier `docker-compose.dev.yml` et configs de dev
---
## 📜 Historique
- **2026-04-14** : Réécriture complète post-audit (v1.0.4). L'ancienne version référençait `backend/`, `frontend/`, `ORIGIN/` à la racine, un chat server Rust et un desktop Electron qui n'existaient pas ou plus. Voir `AUDIT_REPORT.md` pour le détail.
- **2026-02-22** (commit `05d02386d`) : suppression de `veza-chat-server/` (chat intégré au backend Go depuis v0.502).
- **2026-03-03** : release `v1.0.0`.
- **2026-03-13** : tag `v1.0.2`.
- **2026-04-14** : tag `v1.0.3` existant, cible `v1.0.4` pour la release post-cleanup.
- **2026-04-23** : release `v1.0.7` (BFG history rewrite, .git 2.3 GB → 66 MB, transactions marketplace, UserRateLimiter wired).
- **2026-04-26** : release `v1.0.8` (MinIO storage end-to-end, OpenAPI orval migration, drop `@openapitools/openapi-generator-cli` legacy generator, E2E Playwright workflow + `--ci` seed flag, queue+password handler annotations, full authService → orval).
---
_Source de vérité pour le comportement de Claude Code sur Veza. Ne jamais modifier sans commit explicite (`docs: update CLAUDE.md [raison]`)._

View file

@ -70,7 +70,7 @@ Exemples :
- `feat: add adaptive HLS transcoding worker`
- `fix: correct JWT user_id mismatch between Go and Rust`
- `refactor: isolate DM module in stream-server`
- `refactor: isolate DM module in chat-server`
---

159
DEMARRAGE_SIMPLE.md Normal file
View file

@ -0,0 +1,159 @@
# 🚀 Démarrage Simple - Test Intégration Veza
## ✅ Problèmes Corrigés
1. ✅ Migration SQL corrigée (`050_data_validation_constraints.sql`)
2. ✅ Redis démarré correctement
3. ✅ Configuration backend créée (`.env`)
---
## 🎯 Démarrage en 3 Étapes
### Étape 1: Infrastructure Docker
```bash
make infra-up
```
**Vérification**:
```bash
docker compose ps
# Devrait voir: postgres, redis, rabbitmq (tous "healthy")
```
---
### Étape 2: Backend Go
```bash
cd veza-backend-api
# Le fichier .env est déjà créé avec la bonne config
# Si besoin, vérifier:
cat .env
# Démarrer le serveur
go run cmd/api/main.go
```
**Vérification**:
```bash
# Dans un autre terminal
curl http://localhost:8080/health
# Devrait retourner: {"status":"ok"}
```
**URLs**:
- API: http://localhost:8080/api/v1
- Swagger: http://localhost:8080/docs
- Health: http://localhost:8080/health
---
### Étape 3: Frontend React
```bash
cd apps/web
# Démarrer Vite
npm run dev
```
**Vérification**:
- Ouvrir http://localhost:3000 dans le navigateur
- La page devrait se charger
---
## 🧪 Test Complet
1. **Ouvrir** http://localhost:3000
2. **Tester Register**:
- Créer un compte
- Vérifier que ça fonctionne
3. **Tester Login**:
- Se connecter
- Vérifier DevTools → Network → Headers
- Devrait voir `Authorization: Bearer <token>`
- Devrait voir `X-CSRF-Token: <token>` sur les mutations
4. **Tester API**:
- Ouvrir http://localhost:8080/docs
- Tester un endpoint depuis Swagger UI
---
## ⚙️ Configuration
### Backend (`veza-backend-api/.env`)
```bash
APP_ENV=development
JWT_SECRET=dev-secret-key-minimum-32-characters-long-for-testing
DATABASE_URL=postgres://veza:password@localhost:5432/veza?sslmode=disable
REDIS_URL=redis://localhost:6379
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
APP_PORT=8080
LOG_LEVEL=INFO
RABBITMQ_URL=amqp://veza:password@localhost:5672/
```
### Frontend
Aucune configuration nécessaire - valeurs par défaut OK:
- `VITE_API_URL=http://127.0.0.1:8080/api/v1`
---
## 🐛 Si Problème
### Backend ne démarre pas
```bash
# Vérifier DB
docker compose exec postgres psql -U veza -d veza -c "SELECT 1;"
# Vérifier Redis
docker compose exec redis redis-cli ping
# Vérifier logs backend
cd veza-backend-api
go run cmd/api/main.go 2>&1 | tee backend.log
```
### Frontend ne se connecte pas
```bash
# Vérifier CORS
curl -v -H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS \
http://localhost:8080/api/v1/auth/me
# Devrait voir: Access-Control-Allow-Origin: http://localhost:3000
```
### Port occupé
```bash
# Trouver processus
lsof -i :8080 # Backend
lsof -i :3000 # Frontend
# Tuer si nécessaire
kill -9 <PID>
```
---
## ✅ Checklist Finale
- [ ] Infrastructure Docker démarrée (`make infra-up`)
- [ ] Backend démarré sur port 8080
- [ ] Frontend démarré sur port 3000
- [ ] Backend health check OK (`curl http://localhost:8080/health`)
- [ ] Frontend accessible (http://localhost:3000)
- [ ] Swagger accessible (http://localhost:8080/docs)
**Prêt à tester ! 🎉**

View file

@ -1,288 +0,0 @@
# FUNCTIONAL_AUDIT v2 — Veza, ce qu'un utilisateur peut RÉELLEMENT faire
> **Date** : 2026-04-19
> **Branche** : `main` (HEAD = `89a52944e`, `v1.0.7-rc1`)
> **Auditeur** : Claude Code (Opus 4.7 — mode autonome, /effort max, /plan)
> **Méthode** : 5 agents Explore en parallèle + vérifications ponctuelles directes + relecture de `docs/audit-2026-04/v107-plan.md` et `CHANGELOG.md`. **Trace statique** (pas de runtime), comme v1.
> **Supersede** : [v1 du 2026-04-16](#6-diff-vs-audit-v1-2026-04-16). La v1 listait 1 🔴 + 9 🟡. Entre le 16 et aujourd'hui, v1.0.5 → v1.0.7-rc1 ont shippé (50+ commits, la majorité ciblant exactement les findings v1).
> **Ton** : brutal, sans langue de bois. Citations `fichier:ligne`.
---
## 0. Résumé en 5 lignes
1. **Le bloqueur `🔴 Player` de la v1 est résolu.** Un endpoint direct `/api/v1/tracks/:id/stream` avec support Range (`routes_tracks.go:118-120`) sert l'audio sans HLS. Le middleware bypass cache (`response_cache.go:87-104`, commit `b875efcff`) permet le range-request. Le player frontend tombe automatiquement sur `/stream` si HLS échoue (`playerService.ts:280-293`). `HLS_STREAMING=false` reste le default (`config.go:355`) **mais ce n'est plus un blocker** : l'audio sort.
2. **Inscription / vérification email : cassée en v1, corrigée.** `IsVerified: false` (`core/auth/service.go:200`), `VerifyEmail` endpoint réellement vivant, login gate 403 sur unverified (`service.go:527`), MailHog branché par défaut dans `docker-compose.dev.yml`, SMTP env schema unifié (commit `066144352`). Tout le parcours register → mail → click → login fonctionne.
3. **Paiements solidifiés de façon massive.** Refund fait **reverse-charge Hyperswitch avec idempotency-key** (`service.go:1297-1436`). Reconciliation worker sweep les stuck orders/refunds/orphans (`reconcile_hyperswitch.go:55-150`). Webhook raw payload audit (`webhook_log.go`). 5 gauges Prometheus ledger-health + 3 alert rules. **Dev bypass persiste** (simulated payment si `HYPERSWITCH_ENABLED=false`, `service.go:550-586`) **mais `Config.Validate` refuse de booter en prod** sans Hyperswitch (`config.go:908-910`). Fail-closed en prod, fail-open en dev.
4. **Points rugueux restants** : (a) **WebRTC 1:1 sans STUN/TURN** — signaling ✅ mais NAT traversal HS en prod ; (b) **Stockage local disque only** — le code S3/MinIO existe mais n'est pas wiré dans l'upload path ; (c) **HLS toujours off par défaut** → pas d'adaptive bitrate out-of-the-box ; (d) **Transcoding dual-trigger** (gRPC Rust + RabbitMQ) — redondance non documentée.
5. **Verdict** : Veza v1.0.7-rc1 est prêt pour une **démo publique contrôlée** (un seul pod, infra dev, Hyperswitch sandbox). Pour un **déploiement prod multi-pod avec utilisateurs réels** il manque : MinIO wiré, STUN/TURN pour les calls, et la documentation d'exploitation des gauges ledger-health. La surface "un utilisateur lambda peut register → verify → upload → play → acheter → rembourser" est **entièrement opérationnelle**.
---
## 1. Tableau des features — verdict réel au 2026-04-19
Légende : **✅ COMPLET** câblé de bout-en-bout · **🟡 PARTIEL** gotchas exploitables · **🔴 FAÇADE** UI sans backend réel · **⚫ ABSENT**.
| # | Feature | Verdict | v1 | Détail + citation |
| --- | ---------------------------------------------------------------- | :-----: | :-: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Register / Login / JWT / Refresh | ✅ | 🟡 | `IsVerified: false` (`core/auth/service.go:200`). Login 403 si unverified (`service.go:527`). JWT RS256 prod / HS256 dev. |
| 2 | Verify email | ✅ | 🔴 | `POST /auth/verify-email` actif (`routes_auth.go:103-107`). Token généré + stocké en DB, email envoyé via MailHog par défaut. |
| 3 | Forgot / Reset password | ✅ | 🟡 | `password_reset_handler.go:67-250`. Token en DB avec expiry, invalide toutes les sessions à l'usage. |
| 4 | 2FA TOTP | ✅ | ✅ | `internal/handlers/two_factor_handler.go:171`. Obligatoire pour admin. |
| 5 | OAuth (Google/GitHub/Discord/Spotify) | ✅ | ✅ | `routes_auth.go:122-176`. |
| 6 | Profils utilisateur + slug / username | ✅ | ✅ | `profile_handler.go:102`. |
| 7 | Upload de tracks | 🟡 | 🟡 | ClamAV sync ✅ (fail-secure par défaut, `upload_validator.go:87-88`). **Stockage local disque** (`track_upload_handler.go:376`). Dual trigger transcoding (gRPC + RabbitMQ) non doc. |
| 8 | CRUD Tracks / Library | ✅ | ✅ | List / filtres / pagination réels. Library filtrée sur `status=Completed`. |
| 9 | **Player + Queue + écoute audio** | ✅ | 🔴 | **🔴 → ✅** : `/tracks/:id/stream` avec Range (`routes_tracks.go:118-120`, `track_hls_handler.go:266`). Cache bypass wiré (`response_cache.go:87-104`). HLS optionnel, off par défaut. |
| 10 | Playlists (CRUD + share par token) | ✅ | ✅ | `playlist_handler.go:43`. |
| 11 | Queue collaborative (host-authority) | ✅ | ✅ | `queue_handler.go`. |
| 12 | Chat WebSocket (messages, typing, reactions, attachments) | ✅ | 🟡 | DB persist avant broadcast (`handler_messages.go:91-113`). 12 features wirées (edit/delete/typing/read/delivered/reactions/attachments/search/convos/channel/DM/calls). |
| 13 | Chat multi-instance | ✅ | 🟡 | **🟡 → ✅** : Redis pubsub + fallback in-memory **avec log ERROR loud** (`chat_pubsub.go:23-27, 48`). Plus de silent fail. |
| 14 | WebRTC 1:1 calls | 🟡 | 🟡 | Signaling ✅ (`handler.go:89-98`). **STUN/TURN absent** — pas d'env var, pas de grep hit. NAT symétrique = call HS. |
| 15 | Co-listening (listen-together) | ✅ | ✅ | `colistening/hub.go:104-148`, host-authority, keepalive 30s. |
| 16 | **Livestream (RTMP ingest)** | ✅ | 🟡 | **🟡 → ✅** : `/api/v1/live/health` (`live_health_handler.go:78-96`) + banner UI (`useLiveHealth.ts:41-61`, commit `64fa0c9ac`). Plus de silent OBS fail. |
| 17 | Livestream viewer playback | ✅ | ✅ | HLS via nginx-rtmp (`live_stream_callback.go:66`). URL dans `streamURL`. |
| 18 | Dashboard | ✅ | ✅ | `/api/v1/dashboard`. |
| 19 | Recherche (unifiée + tracks) | ✅ | ✅ | `search_handlers.go:41` — ES puis fallback Postgres LIKE + pg_trgm. |
| 20 | Social / Feed / Posts / Groups | ✅ | ✅ | `social.go:161`, chronologique. |
| 21 | Discover (genres/tags déclaratifs) | ✅ | ✅ | `discover.go:49-63`. |
| 22 | Presence + rich presence | ✅ | ✅ | `presence_handler.go:30-46`. |
| 23 | Notifications + Web Push | ✅ | ✅ | `notification_handlers.go:197`. |
| 24 | **Marketplace + checkout** | ✅ | 🟡 | Hyperswitch wiré (`service.go:522-548`). **Simulated payment si dev** (`:550-586`) **mais `Config.Validate` refuse prod sans Hyperswitch** (`config.go:908-910`). Cart côté server ✅. |
| 25 | **Refund (reverse-charge)** | ✅ | 🟡 | **🟡 → ✅** : 3 phases avec idempotency-key `refund.ID` (`service.go:1297-1436`, commits `4f15cfbd9` `959031667`). Webhook handler wiré. |
| 26 | Hyperswitch reconciliation sweep | ✅ | ⚫ | **⚫ → ✅** (nouveauté v1.0.7) : `reconcile_hyperswitch.go:55-150` couvre stuck orders/refunds/orphans, 10 tests green. |
| 27 | Webhook raw payload audit log | ✅ | ⚫ | **⚫ → ✅** (v1.0.7) : `webhook_log.go:34-80` + cleanup 90j (`cleanup_hyperswitch_webhook_log.go`). |
| 28 | Ledger-health metrics + alerts | ✅ | ⚫ | **⚫ → ✅** (v1.0.7 item F) : 5 gauges Prometheus + 3 alert rules Alertmanager + dashboard Grafana. |
| 29 | Seller dashboard + Stripe Connect payout | ✅ | ✅ | `sell_handler.go`, transfer auto post-webhook. |
| 30 | **Stripe Connect reversal (async)** | ✅ | 🟡 | **🟡 → ✅** (v1.0.7 items A+B) : `reversal_worker.go:12-180`, state machine `reversal_pending`, `stripe_transfer_id` persisté, exp. backoff 1m→1h. |
| 31 | Reviews / Factures | ✅ | ✅ | DB + handlers wirés. |
| 32 | Subscription plans | ✅ | 🟡 | **🟡 → ✅** (v1.0.6.2 hotfix `d31f5733d`) : `hasEffectivePayment()` gate (`subscription/service.go:140-155`). Plus de bypass. |
| 33 | Distribution plateformes externes | ✅ | ✅ | `distribution_handler.go:32-62`. |
| 34 | Formation / Education | ✅ | ✅ | `education_handler.go:33` — DB-backed. |
| 35 | Support tickets | ✅ | ✅ | `support_handler.go:54-100`. |
| 36 | Developer portal (API keys + webhooks) | ✅ | ✅ | `routes_developer.go:11`. |
| 37 | Analytics (creator stats) | ✅ | ✅ | `playback_analytics_handler.go`, CSV/JSON export. |
| 38 | Admin — dashboard / users / modération / flags / audit | ✅ | 🟡 | `admin/handler.go:43-54`. **Maintenance mode 🟡 → ✅** via `platform_settings` + TTL 10s (`middleware/maintenance.go:16-100`, commit `3a95e38fd`). |
| 39 | Admin — transfers (v0.701) | ✅ | ✅ | `admin_transfer_handler.go:36-91`. |
| 40 | Self-service creator role upgrade | ✅ | ⚫ | **⚫ → ✅** (commit `c32278dc1`) : `POST /users/me/upgrade-creator` gate email-verified, idempotent. |
| 41 | Upload-size SSOT | ✅ | ⚫ | **⚫ → ✅** (commit `5848c2e40`) : `config/upload_limits.go` + `GET /api/v1/upload/limits` consommé par `useUploadLimits` côté web. |
| 42 | Tag suggestions | ✅ | ✅ | `tag_handler.go:15-32`. |
| 43 | PWA (install + service worker + wake lock) | ✅ | ✅ | `components/pwa/`, v0.801. |
| 44 | Orphan tracks cleanup | ✅ | ⚫ | **⚫ → ✅** (commit `553026728`) : `jobs/cleanup_orphan_tracks.go`, hourly, flip `processing`→`failed` si fichier disque manquant. |
| 45 | Stem upload & sharing (F482) | ✅ | ✅ | `routes_tracks.go:185-189`, ownership guard. |
**Score** : 43 ✅ / 2 🟡 / 0 🔴 / 0 ⚫. La seule 🔴 de la v1 (Player/écoute audio) est résolue.
**Les 2 🟡 restants** : **Upload** (stockage local disque → pas prêt pour production scale) et **WebRTC 1:1** (pas de STUN/TURN → NAT traversal HS).
---
## 2. Les 6 parcours — étape par étape
### Parcours 1 — Écouter de la musique
**Verdict : ✅ OPÉRATIONNEL.** Le bloqueur v1 est résolu — le fallback direct stream existe.
| # | Étape | Verdict | Preuve |
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Créer un compte | ✅ | `POST /auth/register``core/auth/service.go:104-469`. `IsVerified: false` (`:200`), token en DB. |
| 2 | Recevoir l'email | ✅ | MailHog par défaut dans `docker-compose.dev.yml:114-130`. UI sur port 8025. Prod : 500 hard si SMTP down (`service.go:387`). |
| 3 | Cliquer le lien verify | ✅ | `POST /auth/verify-email?token=X``core/auth/service.go:747-765` check token + flip `is_verified=true`. |
| 4 | Se connecter | ✅ | `POST /auth/login` → 403 Forbidden si `!IsVerified` (`service.go:527`). Lockout après 5 tentatives / 15 min. |
| 5 | Chercher un morceau | ✅ | `GET /api/v1/search``search_handlers.go:41`, ES ou fallback Postgres tsvector. |
| 6 | Lancer la lecture | ✅ | Player React tente HLS d'abord (`playerService.ts:283-293`), fallback direct `/stream`. |
| 7 | **Le son sort ?** | ✅ | `GET /tracks/:id/stream` avec `http.ServeContent` (`track_hls_handler.go:266`), Range supporté, cache bypass wiré (`response_cache.go:87-104`). |
**Piège dev** : si on upload un fichier mais que le transcoding (Rust stream server) échoue, le track reste en `Processing`. Le cleanup worker hourly le flippera à `Failed` après 1h. Le fichier **reste lisible via `/stream`** pendant ce temps, mais il n'apparaît pas en library (filtre `status=Completed`).
### Parcours 2 — Uploader un morceau (artiste)
**Verdict : ✅ MAIS sur local disque.**
| # | Étape | Verdict | Preuve |
| --- | --------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Login | ✅ | Comme parcours 1. |
| 2 | Upgrade creator (si besoin) | ✅ | `POST /api/v1/users/me/upgrade-creator` — gate email-verified, idempotent (`upgrade_creator_handler.go`). UI `AccountSettingsCreatorCard.tsx`. |
| 3 | Uploader un fichier audio | ✅ | `POST /api/v1/tracks/upload``track_upload_handler.go:39-171`. Multipart, taille SSOT (`config/upload_limits.go`), ClamAV **sync** fail-secure. |
| 4 | Stockage physique | 🟡 | **`uploads/tracks/<userID>/<filename>` sur disque local** (`track_upload_handler.go:376`). Code S3/MinIO présent mais **non wiré** dans ce chemin. |
| 5 | Transcoding | 🟡 | **Dual-trigger** : gRPC Rust stream server (`stream_service.go:49`) **et** RabbitMQ job (`EnqueueTranscodingJob`). Redondance non documentée. |
| 6 | Track visible en library | ✅ | Après `status=Completed`. Avant : utilisateur voit son upload en "Processing" dans son tableau de bord. |
| 7 | Autre user peut trouver/lire| ✅ | Via search + parcours 1. Si track reste `Processing` (transcoding down) → pas en library mais `/tracks/:id/stream` sert quand même le raw. |
### Parcours 3 — Acheter sur le marketplace
**Verdict : ✅ (sandbox testing) + solidifiés massivement depuis v1.**
| # | Étape | Verdict | Preuve |
| --- | ---------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Browse produits | ✅ | `GET /api/v1/marketplace/products`, handlers DB réels. |
| 2 | Ajouter au panier | ✅ | `POST /api/v1/cart/items``cart.go:25-97`, DB-backed (table `cart_items`). |
| 3 | Checkout | ✅ | `POST /api/v1/orders``service.go:522-548` (prod flow Hyperswitch) ou `:550-586` (dev simulated). |
| 4 | **Paiement Hyperswitch** | ✅ | `paymentProvider.CreatePayment()` avec `Idempotency-Key: order.ID` (commit `4f15cfbd9`). Retourne `client_secret` consommé par `CheckoutPaymentForm.tsx`. |
| 5 | Webhook paiement | ✅ | `POST /api/v1/webhooks/hyperswitch` → raw payload logged (`webhook_log.go`), signature HMAC-SHA512 vérifiée, dispatcher `ProcessPaymentWebhook`. |
| 6 | Reconciliation si webhook perdu | ✅ | `reconcile_hyperswitch.go` sweep stuck orders > 30m avec payment_id non vide, synthèse webhook → `ProcessPaymentWebhook`. Idempotent. Configurable `RECONCILE_INTERVAL=1h` (5m pendant incident). |
| 7 | Confirmation + accès contenu | ✅ | Création licenses dans la transaction (`service.go:561-585`), lock `FOR UPDATE` pour exclusive. |
| 8 | Remboursement | ✅ | 3-phase `service.go:1297-1436` : pending row → `CreateRefund` PSP → persist `hyperswitch_refund_id`. Webhook `refund.succeeded` révoque licenses + débite vendeur. |
| 9 | Reverse-charge Stripe Connect | ✅ | `reversal_worker.go:12-180`, state `reversal_pending`, async, backoff 1m→1h. Rows pré-v1.0.7 sans `stripe_transfer_id``permanently_failed` avec message explicite. |
**Piège prod** : `HYPERSWITCH_ENABLED=false` = dev bypass. **Garde-fou** : `Config.Validate` refuse de booter en prod si `HYPERSWITCH_ENABLED=false` (`config.go:908-910`) — message explicite "marketplace orders complete without charging, effectively giving away products". Fail-closed au bon endroit.
### Parcours 4 — Chat
**Verdict : ✅ sur toutes les surfaces.**
| # | Étape | Verdict | Preuve |
| --- | ------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Ouvrir le chat | ✅ | `apps/web/src/features/chat/pages/ChatPage.tsx`. |
| 2 | Rejoindre / créer une room | ✅ | `POST /api/v1/conversations``CreateRoom:54`. |
| 3 | Envoyer un message | ✅ | WS dispatcher `handler.go:54-106``HandleSendMessage:18` → DB **avant** broadcast (`handler_messages.go:91-113`). |
| 4 | Recevoir (temps réel) | ✅ | Hub local, puis PubSub pour multi-instance. |
| 5 | Persistance | ✅ | `chat_messages` table, indexed. |
| 6 | Multi-instance sans Redis | ✅ | Fallback in-memory **avec log ERROR loud** ("Redis unavailable, cross-instance messages will be lost") (`chat_pubsub.go:23-27`). Plus de silent fail. |
| 7 | Typing / reactions / attach. | ✅ | 12 features wirées (voir §1 ligne 12). |
### Parcours 5 — Livestream
**Verdict : ✅ avec banner UI si RTMP down.**
| # | Étape | Verdict | Preuve |
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Démarrer un live | ✅ | `POST /api/v1/live/streams``live_stream_handler.go:71-98`, génère `stream_key` UUID + `rtmp_url`. |
| 2 | Push OBS → nginx-rtmp | ✅ | `on_publish` callback `live_stream_callback.go:38-80` avec secret `X-RTMP-Callback-Secret`, flip `is_live=true`. |
| 3 | Health check visible | ✅ | `GET /api/v1/live/health` (`live_health_handler.go:78-96`) + poll 15s front (`useLiveHealth.ts:41-61`). Banner warn si `rtmp_reachable=false`.|
| 4 | Viewer play live | ✅ | HLS via nginx-rtmp (`streamURL` = `baseURL + /{streamKey}/playlist.m3u8`). |
| 5 | Co-listening en parallèle| ✅ | Feature séparée, `colistening/hub.go:104-148`, host-authority sync 100ms drift threshold. |
**Piège** : nécessite `docker compose --profile live up` pour démarrer nginx-rtmp. Sans ça, banner red immédiat. Plus de silent fail comme en v1.
### Parcours 6 — Admin
**Verdict : ✅ complet avec persistance maintenance mode.**
| # | Étape | Verdict | Preuve |
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------ |
| 1 | Accéder /admin | ✅ | Middleware JWT + role check, 2FA obligatoire. |
| 2 | Voir stats | ✅ | `admin/handler.go:43-54` `GetPlatformMetrics`. |
| 3 | Modérer (queue, bans) | ✅ | `moderation/handler.go:44` `GetModerationQueue`, ban/suspend wirés. |
| 4 | Gérer utilisateurs | ✅ | Admin handlers (user upgrade, role change). |
| 5 | Maintenance mode | ✅ | Persisté `platform_settings` (`middleware/maintenance.go:16-100`, TTL 10s). Survit au restart. **🟡 v1 → ✅ v2**. |
| 6 | Feature flags | ✅ | DB-backed. |
| 7 | Ledger health dashboard | ✅ | Grafana `config/grafana/dashboards/ledger-health.json` + 5 gauges + 3 alert rules (voir §1 ligne 28). |
| 8 | Admin transfers | ✅ | `admin_transfer_handler.go:36-91`, manual retry, state machine persistée. |
---
## 3. Carte des dépendances
### 3.1 Services — hard-required vs optionnels
| Service | Status | Comportement si down | Preuve |
| -------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| **PostgreSQL** | 🔴 Hard-req | App panique au boot (`main.go:112-120`, migrations auto-run). | `db.Initialize()` + `RunMigrations()` fatal. |
| **Migrations** | 🔴 Auto | Appliquées au démarrage, boot fail si erreur SQL. | `database.go:234-256`. |
| **Redis** | 🟢 Dégradation | TokenBlacklist nil-safe. Chat PubSub fallback in-memory avec **log ERROR loud**. Rate limiter dégradé. | `chat_pubsub.go:23-27` ; `config.go:55-58`. |
| **RabbitMQ** | 🟢 Dégradation | EventBus publish failures maintenant **loggés ERROR** (commit `bf688af35`) au lieu de silent drop. | `main.go:128-139` ; `config.go:690-693`. |
| **MinIO / S3** | 🟢 Non utilisé | `AWS_S3_ENABLED=false` par défaut, **code S3 présent mais non wiré dans upload path**. Disque local always. | `config.go:697-720` ; `track_upload_handler.go:376`. |
| **Elasticsearch** | 🟢 Optionnel | Search fallback Postgres full-text search (tsvector + pg_trgm). ES non utilisé en chemin chaud. | `fulltext_search_service.go:14-30` ; `main.go:288-297` (cleanup only). |
| **ClamAV** | 🟠 Fail-secure | `CLAMAV_REQUIRED=true` par défaut → upload **rejeté** (503) si down. `=false` = bypass avec warning. | `upload_validator.go:87-88, 140-150` ; `services_init.go:27-46`. |
| **Hyperswitch** | 🟠 Prod-gate | `HYPERSWITCH_ENABLED=false` = dev bypass. **Prod : `Config.Validate` refuse boot** si false. | `config.go:908-910` ; `service.go:522-548, 550-586`. |
| **Stripe Connect** | 🟠 Prod-gate | Reversal worker tourne si config présente. Rows pre-v1.0.7 sans id → `permanently_failed`. | `reversal_worker.go:12-180` ; `main.go:188`. |
| **Nginx-RTMP** | 🟢 Profil live | `docker compose --profile live up`. Si down : banner UI immédiat sur Go Live page. | `live_health_handler.go:78-96` ; `useLiveHealth.ts:41-61`. |
| **Rust stream srv** | 🟢 Optionnel | HLS gated `HLSEnabled=false` default. Direct `/stream` fallback toujours disponible. Transcoding async. | `stream_service.go:49` ; `config.go:355` ; `track_hls_handler.go:266`. |
| **MailHog (SMTP)** | 🟢 Dev default | Branché `docker-compose.dev.yml:114-130`, port 1025. Dev : fail email → log + continue. Prod : 500 hard. | `.env.template:160-165` ; `service.go:381-407`. |
**Résumé** : **3 hard-required** (Postgres, migrations, bcrypt) · **le reste est optionnel avec fallback, fail-secure, ou prod-gate explicite**. C'est l'évolution la plus importante depuis v1 : il n'y a plus de silent failures non documentés.
### 3.2 Seeding
- `veza-backend-api/cmd/tools/seed/main.go` : modes `production` / `full` / `smoke`. Truncate tables → insert users → tracks → playlists → social → chat. **Manuel**, pas auto-run. Marche.
---
## 4. Stabilité — points de fragilité restants
| # | Fragilité | Impact | Preuve |
| -- | ------------------------------------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | **WebRTC 1:1 sans STUN/TURN** | 🟡 Prod | Pas d'env var, pas de grep hit. NAT symétrique = call failures silencieuses (les signals passent, mais le flux média échoue). |
| 2 | **Stockage local disque only** | 🟡 Prod | `uploads/tracks/<userID>/` sur FS local. Pas scalable multi-pod sans volume partagé. Le code S3/MinIO est dead in upload path. |
| 3 | **HLS `HLSEnabled=false` par défaut** | 🟢 Dev | Fonctionnel grâce au fallback `/stream`. Pas d'adaptive bitrate out-of-box. Opérateur doit activer explicitement. |
| 4 | **Transcoding dual-trigger** | 🟡 Ops | `StreamService.StartProcessing` (gRPC) **et** `EnqueueTranscodingJob` (RabbitMQ) appelés tous les deux. Redondance non documentée. |
| 5 | **`HLS_STREAMING` absent de .env.template** | 🟠 Doc | Dev qui veut HLS doit trouver la var ailleurs. `.env.template` à compléter. |
| 6 | **Dev bypass Hyperswitch** | 🟢 Ops | Fail-closed prod (`Config.Validate`), mais en staging un opérateur distrait peut servir des licences gratuites. Mettre un warning loud au boot. |
| 7 | **Email tokens en query param** | 🟠 Sec | `?token=X` peut leak via Referer / logs proxy. Migration flagged v0.2 (commentaire `handlers/auth.go` L339). |
| 8 | **Register issue JWT avant email send** | 🟠 UX | User a ses tokens avant que l'email parte → login 403 immédiat tant que non-vérifié. Cohérent mais friction. |
| 9 | **ClamAV 10s timeout sync** | 🟢 UX | Upload bloque jusqu'à 10s sur scan. Acceptable pour fichiers audio <100MB. |
| 10 | **Subscription `pending_payment` item G** | 🟢 Roadm| v1.0.6.2 compense via filter, item G dans v107-plan refait le path proprement. Pas un bug, juste techdebt flaggée. |
**Zero silent fails** parmi les 6 surfaces critiques (Chat Redis, RabbitMQ, RTMP, HLS, SMTP, Hyperswitch). C'est le grand changement depuis v1.
---
## 5. Verdict final
**Veza v1.0.7-rc1 est prêt pour :**
- ✅ **Démo publique contrôlée** — un pod, infra dev `make dev`, Hyperswitch sandbox. Le parcours "register → verify → search → play → upload → purchase → refund" est intégralement opérationnel.
- ✅ **Sandbox payment testing** — refund réel, reconciliation, ledger-health gauges, Stripe Connect reversal. Toute la plomberie monétaire est audit-ready.
- ✅ **Beta privée multi-utilisateurs** — chat multi-instance avec alarme loud si Redis manque, co-listening host-authority, livestream avec health banner. Pas de silent fails.
**Veza v1.0.7-rc1 n'est PAS prêt pour :**
- 🟡 **Production publique grand-public scale** — le stockage uploads sur disque local ne survit pas à un second pod. MinIO/S3 doit être wiré dans le path upload (le code dort, il faut juste l'appeler).
- 🟡 **Calls WebRTC fiables hors LAN** — sans STUN/TURN, symmetric NAT = échec silencieux du flux média. À configurer avant d'ouvrir la feature calls au public.
- 🟠 **Opérateur ops naïf** — le dashboard Grafana ledger-health est là mais ne sert à rien si personne ne le regarde. Nécessite un runbook d'exploitation.
**Ce qui a changé depuis la v1 du 2026-04-16** — en 3 jours, l'équipe a fermé **7 findings 🔴/🟡** et ajouté **10 nouvelles capacités** (reconciliation, audit log webhook, ledger metrics, reversal async, upgrade creator, upload SSOT, RTMP health, orphan cleanup, maintenance persist, SMTP unified). Voir §6.
**En une phrase** : **le code est solide, la plomberie est honnête, les seuls 🟡 restants sont des features "scale" (storage, NAT) pas des bugs**.
---
## 6. Diff vs audit v1 (2026-04-16)
Tableau des évolutions : chaque ligne = un finding v1 avec son statut aujourd'hui.
| Finding v1 | v1 | v2 | Commit / Preuve |
| ---------------------------------------------------------- | :-: | :-: | ------------------------------------------------------------------------------------------------------ |
| Player/écoute audio sans fallback (HLSEnabled=false) | 🔴 | ✅ | Endpoint direct `/tracks/:id/stream` + Range cache bypass. `b875efcff`, `routes_tracks.go:118-120`. |
| Register : `IsVerified: true` hardcoded | 🔴 | ✅ | `service.go:200``IsVerified: false`. Commit trail. |
| Verify email : dead code | 🔴 | ✅ | Endpoint actif, login 403 sur unverified (`service.go:527`). |
| SMTP silent fail | 🟡 | ✅ | Env schema unifié (`066144352`). Prod : 500 hard. Dev : log + continue. MailHog branché par défaut. |
| Marketplace dev bypass | 🟡 | ✅ | Prod gate `Config.Validate` refuse boot (`config.go:908-910`). Dev bypass conservé, assumé. |
| Refund : row DB only, pas de reverse-charge | 🟡 | ✅ | 3-phase avec idempotency key. `959031667`, `4f15cfbd9`, `service.go:1297-1436`. |
| Subscription : payment gate bypass | 🟡 | ✅ | v1.0.6.2 hotfix `d31f5733d`, `hasEffectivePayment()`. |
| Chat multi-instance silent fallback | 🟡 | ✅ | Redis missing = **log ERROR loud** (`chat_pubsub.go:23-27`). Fallback conservé pour single-pod dev. |
| Livestream : dépendance cachée `--profile live` | 🟡 | ✅ | Health endpoint + banner UI (`64fa0c9ac`, `live_health_handler.go:78-96`). |
| Maintenance mode in-memory | 🟡 | ✅ | Persisté `platform_settings` + TTL 10s. `3a95e38fd`, `middleware/maintenance.go:16-100`. |
| Tracks orphelines `Processing` indéfiniment | 🟡 | ✅ | Cleanup hourly worker. `553026728`, `jobs/cleanup_orphan_tracks.go`. |
| RabbitMQ silent drop | 🟡 | ✅ | Log ERROR sur publish failure. `bf688af35`. |
| Upload size limits désalignés front/back | 🟠 | ✅ | SSOT `config/upload_limits.go` + hook `useUploadLimits`. `5848c2e40`. |
| Stripe Connect reversal inexistant | 🔵 | ✅ | Async worker + state machine `reversal_pending`. v1.0.7 items A+B. |
| Reconciliation Hyperswitch (stuck orders) | 🔵 | ✅ | `reconcile_hyperswitch.go:55-150`. v1.0.7 item C. |
| Webhook raw payload audit log | 🔵 | ✅ | `webhook_log.go` + cleanup 90j. v1.0.7 item E. |
| Ledger-health metrics + alerts | 🔵 | ✅ | 5 gauges Prometheus + 3 alert rules + Grafana dashboard. v1.0.7 item F. |
| Idempotency-key Hyperswitch | 🔵 | ✅ | Sur CreatePayment + CreateRefund. v1.0.7 item D (`4f15cfbd9`). |
| Self-service creator upgrade | 🔵 | ✅ | `POST /users/me/upgrade-creator`, email-verified gate. `c32278dc1`. |
| WebRTC sans STUN/TURN | 🟡 | 🟡 | **Toujours pas fixé.** Signaling ok, NAT traversal non. |
| Stockage uploads sur disque local | 🟡 | 🟡 | **Toujours pas fixé.** Code S3 présent, non wiré. |
| HLS `HLSEnabled=false` par défaut | 🔴 | 🟢 | Plus bloquant grâce au fallback direct stream, mais flag toujours off. |
Légende : 🔵 = finding absent de v1 mais identifié ici, 🟢 = non-bloquant en v2, 🟠 = doc/cleanup.
**Bilan** : **18 findings v1 résolus**, **2 subsistants** (WebRTC TURN, stockage local). **7 nouvelles capacités ajoutées** (reconcil, audit log, ledger metrics, reversal, upgrade creator, upload SSOT, RTMP health). Le "chemin critique v1.0.5 public-ready" listé en v1 est **intégralement réalisé** par v1.0.5 → v1.0.7-rc1.
---
## 7. Cleanup session post-rc1 (2026-04-23)
Une session cleanup + BFG a été exécutée 4 jours après cet audit. Cross-référence avec [AUDIT_REPORT.md §9](AUDIT_REPORT.md) :
- ✅ **10/15 items Top-15 traités** (cleanup #1/#2/#3/#6/#7/#9/#11/#12/#13, BFG inclus)
- ⚠️ **3 false-positives identifiés** (#4 context propagation, #5 security headers, #10 `RespondWithAppError`) — voir `AUDIT_REPORT.md §9.bis` pour les preuves
- 📋 **2 deferrals v1.0.8** (#8 OpenAPI typegen, #14 E2E Playwright CI)
- 📝 **1 item pending** (#15 `docs/ENV_VARIABLES.md` sync, 0.5j)
- **Repo `.git` : 1.5 GB → 66 MB** (97%) après 2 passes git-filter-repo + force-push stages 1+2
Les 2 findings fonctionnels subsistants (WebRTC STUN/TURN + stockage uploads disque local) restent **post-v1.0.7-final** dans le scope v1.0.8 (2-3j chacun).
---
*Généré par Claude Code Opus 4.7 (1M context, /effort max, /plan) — 5 agents Explore parallèles + vérifications ponctuelles directes (`routes_tracks.go:118`, `core/auth/service.go:200`, `config.go:355/907-910`, `marketplace/service.go:522-586`). Cross-référencé avec `docs/audit-2026-04/v107-plan.md` et `CHANGELOG.md` v1.0.5 → v1.0.7-rc1. Une correction par rapport à v1 : le Player n'est plus 🔴 — la v1 avait loupé l'endpoint `/stream` (fallback direct avec Range support). §7 ajouté 2026-04-23 post-session cleanup.*

661
LICENSE Normal file
View file

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View file

@ -34,7 +34,7 @@ include make/help.mk
# Add new services in make/config.mk (SERVICES, SERVICE_DIR_*, PORT_*).
# ==============================================================================
.PHONY: dev-web dev-backend-api dev-stream-server
.PHONY: test-web test-backend-api test-stream-server
.PHONY: lint-web lint-backend-api lint-stream-server
.PHONY: dev-web dev-backend-api dev-chat-server dev-stream-server
.PHONY: test-web test-backend-api test-chat-server test-stream-server
.PHONY: lint-web lint-backend-api lint-chat-server lint-stream-server
# (targets defined in make/dev.mk and make/test.mk)

127
Makefile.old Normal file
View file

@ -0,0 +1,127 @@
# Veza Platform - Root Makefile
# Test Coverage targets (T0043)
.PHONY: test-coverage coverage-html help
help: ## Show this help message
@echo 'Usage: make [target]'
@echo ''
@echo 'Test Coverage targets:'
@echo ' test-coverage - Run tests and generate coverage report (T0043)'
@echo ' coverage-html - Generate HTML coverage report from existing coverage.out (T0043)'
test-coverage: ## Run tests and generate coverage report (T0043)
@echo "📊 Generating test coverage report..."
@bash scripts/test-coverage.sh
coverage-html: ## Generate HTML coverage report from existing coverage.out (T0043)
@echo "📊 Generating HTML coverage report..."
@cd veza-backend-api && go tool cover -html=coverage/coverage.out -o coverage/coverage.html
@echo "✅ Coverage report generated: veza-backend-api/coverage/coverage.html"
# >>> VEZA:BEGIN QA TARGETS
.PHONY: smoke e2e postman lighthouse load qa-all visual backstop-ref backstop-test loki lh a11y start-services
smoke: ## Run API smoke tests (curl + httpie)
@echo "🔥 Running API smoke tests..."
@bash .veza/qa/scripts/wait_for_http.sh "$${VEZA_API_BASE_URL:-http://localhost:8080}/health" 90
@bash .veza/qa/scripts/smoke_curl.sh
@bash .veza/qa/scripts/smoke_httpie.sh || true
start-services: ## Start services required for QA tests
@echo "🚀 Starting services for QA tests..."
@bash .veza/qa/scripts/start-services-for-tests.sh
e2e: ## Run E2E tests with Playwright
@echo "🎭 Running E2E tests..."
@cd .veza/qa/playwright && \
if [ ! -d "node_modules" ] || [ ! -f "node_modules/@playwright/test/package.json" ]; then \
echo "📦 Installing Playwright dependencies..."; \
npm install --silent; \
fi && \
npx playwright test --config=playwright.config.ts
postman: ## Run Postman/Newman tests
@echo "📮 Running Postman/Newman tests..."
@newman run .veza/qa/postman/veza_api_collection.json \
-e .veza/qa/data/postman_env_local.json \
--reporters cli,junit \
--reporter-junit-export reports/newman.xml || true
lighthouse: ## Run Lighthouse CI
@echo "💡 Running Lighthouse CI..."
@npx lhci autorun --config=.veza/qa/lighthouse/lighthouserc.json || true
load: ## Run k6 load tests
@echo "⚡ Running k6 load tests..."
@k6 run .veza/qa/k6/smoke.js || true
visual: ## Run Playwright visual regression tests
@echo "🖼️ Running Playwright visual regression tests..."
@cd .veza/qa/playwright && \
if [ ! -d "node_modules" ] || [ ! -f "node_modules/@playwright/test/package.json" ]; then \
echo "📦 Installing Playwright dependencies..."; \
npm install --silent; \
fi && \
npx playwright test tests/visual/ --config=playwright.config.ts
visual-update: ## Generate/update Playwright visual snapshots
@echo "📸 Generating Playwright visual snapshots..."
@cd .veza/qa/playwright && \
if [ ! -d "node_modules" ] || [ ! -f "node_modules/@playwright/test/package.json" ]; then \
echo "📦 Installing Playwright dependencies..."; \
npm install --silent; \
fi && \
npx playwright test tests/visual/ --config=playwright.config.ts --update-snapshots
backstop-ref: ## Generate BackstopJS reference images
@echo "📸 Generating BackstopJS reference images..."
@cd .veza/qa/backstop && npx backstop reference --config=backstop.json || true
backstop-test: ## Run BackstopJS visual regression tests
@echo "🔍 Running BackstopJS visual regression tests..."
@cd .veza/qa/backstop && npx backstop test --config=backstop.json || true
loki: ## Run Loki visual regression tests (requires Storybook)
@echo "📚 Running Loki visual regression tests..."
@echo "⚠️ Loki requires Storybook to be set up. See .veza/qa/README.md for setup instructions."
@if [ -d ".storybook" ] || [ -d "apps/web/.storybook" ]; then \
npx loki test || true; \
else \
echo "❌ Storybook not found. Install Storybook first to use Loki."; \
exit 1; \
fi
lh: lighthouse ## Alias for lighthouse
a11y: ## Run Pa11y accessibility tests
@echo "♿ Running Pa11y accessibility tests..."
@npx pa11y-ci --config .veza/qa/pa11y/.pa11yci.json || true
qa-all: smoke e2e postman lighthouse load visual a11y ## Run all QA tests
@echo "✅ All QA tests completed!"
# <<< VEZA:END QA TARGETS
# >>> VEZA:BEGIN LAB ORCHESTRATION
.PHONY: infra-up infra-check migrate-all services-up health-all dev-lab
infra-up: ## Start Lab Infrastructure (Postgres, Redis, RabbitMQ)
@bash scripts/lab/start_infra.sh
infra-check: ## Check Lab Infrastructure Health
@bash scripts/lab/check_infra.sh
migrate-all: ## Apply migrations for all services
@bash scripts/lab/apply_all_migrations.sh
services-up: ## Start all services (Backend, Chat, Stream, Web)
@bash scripts/lab/start_all_services.sh
services-down: ## Stop all services
@bash scripts/lab/stop_all_services.sh
health-all: ## Check health of all services
@bash scripts/lab/check_all_health.sh
dev-lab: infra-up infra-check migrate-all services-down services-up health-all ## Start full Lab Environment (Clean Restart)
# <<< VEZA:END LAB ORCHESTRATION

View file

@ -1,54 +1,32 @@
# Veza Monorepo
[![CI](https://github.com/okinrev/veza/actions/workflows/ci.yml/badge.svg)](https://github.com/okinrev/veza/actions/workflows/ci.yml)
**Version courante** : v1.0.4 (cleanup + consolidation post-audit). Voir [CHANGELOG.md](CHANGELOG.md) et [docs/PROJECT_STATE.md](docs/PROJECT_STATE.md).
**Version cible** : v0.101 (stabilisation en cours). Voir [docs/V0_101_RELEASE_SCOPE.md](docs/V0_101_RELEASE_SCOPE.md) pour le périmètre.
## Project Structure
- **`apps/web`** — Frontend React 18 + Vite 5 + TypeScript strict (source of truth for the UI)
- **`veza-backend-api`** — Main Go 1.25 API service (Gin, GORM, Postgres, Redis, RabbitMQ, Elasticsearch). Handles REST, WebSocket, and chat (chat server was merged into this service in v0.502).
- **`veza-stream-server`** — Rust streaming server (Axum 0.8, Tokio 1.35, Symphonia) — HLS, HTTP Range, WebSocket, gRPC
- **`veza-common`** — Shared Rust types and logging
- **`packages/design-system`** — Shared design tokens
See [CLAUDE.md](CLAUDE.md) for the full architecture map.
## Development Setup
Prerequisites: Node 20 (see `.nvmrc`), Go, Rust, Docker. Configure `.env` from `.env.example`.
```bash
# Verify environment
make doctor
./scripts/validate-env.sh development
# Install dependencies
make install-deps
# Option A — Backend in Docker + Web local
make dev
# Option B — All apps local with hot reload (infra from docker-compose.dev.yml)
make dev-full
# Option C — Infra only, then run services manually
docker compose -f docker-compose.dev.yml up -d
make dev-web # or make dev-backend-api, make dev-stream-server
```
See [docs/ENV_VARIABLES.md](docs/ENV_VARIABLES.md) for required variables. `make build` builds all services.
- **`apps/web`**: The main frontend application (React + Vite). **This is the single source of truth for the UI.**
- **`veza-desktop`**: A thin Electron wrapper that loads `apps/web`. It creates the native desktop experience.
- **`veza-backend-api`**: Main Go API service.
- **`veza-stream-server`**: Rust streaming server.
- **`veza-chat-server`**: Rust chat server.
## Quick Start
### Frontend only
### Frontend
```bash
cd apps/web
npm install
npm run dev
```
### Desktop (Optional)
Requires `apps/web` to be running.
```bash
cd veza-desktop
npm install
npm run dev
```
## Docker Production
**Canonical production compose file**: `docker-compose.prod.yml`
@ -57,16 +35,12 @@ npm run dev
docker compose -f docker-compose.prod.yml up -d
```
**Deprecated** (use docker-compose.prod.yml):
- `docker-compose.production.yml` — legacy, may be removed
- `config/docker/docker-compose.production.yml` — legacy config
See `make/config.mk` for COMPOSE_PROD and deployment docs.
## CI/CD
- **Badge** : CI status above. Set `SLACK_WEBHOOK_URL` (Incoming Webhook) in repo secrets to receive Slack notifications on failure.
### Disabled workflows
- **Storybook** (`chromatic.yml.disabled`, `storybook-audit.yml.disabled`, `visual-regression.yml.disabled`): deferred until MSW is wired up for `/api/v1/auth/me` and `/api/v1/logs/frontend`, which currently causes ~1 400 network errors in the Storybook build. The npm scripts (`storybook`, `build-storybook`) still work locally for one-off component inspection. To reactivate in CI, fix the MSW handlers and rename the three files back to `.yml`.
## Documentation
- **[Developer Onboarding](docs/ONBOARDING.md)** — Setup, architecture, conventions, troubleshooting

View file

@ -1,122 +0,0 @@
# Release Notes — Veza v1.0.0
**Date de release** : 2026-03-03
**Version précédente** : v0.803 (2026-02-25)
---
## Résumé
Veza v1.0.0 est la première release commerciale de la plateforme audio collaborative. Cette version consolide les corrections de sécurité, les améliorations de qualité, et les fonctionnalités livrées entre v0.803 et v1.0.0.
---
## Nouvelles fonctionnalités depuis v0.803
### Sécurité (v0.901v0.903)
- OAuth : génération JWT corrigée via JWTService/SessionService
- Webhook Hyperswitch : vérification de signature obligatoire
- TokenBlacklist intégré au middleware auth (tokens révoqués rejetés)
- ValidateExecPath sur les appels exec (waveform_service)
- Rate limiter : login/register inclus dans le global limit
- Harmonisation Go 1.24, VERSION synchronisé
### Auth & Commerce (v0.911v0.912)
- Tests d'intégration OAuth Google/GitHub E2E
- Tests E2E paiement Hyperswitch, webhook idempotence, refund flow
### Qualité (v0.921v0.923)
- Couverture tests Rust > 30%
- Réduction des skips Go, tests de contrat API
- OpenAPI spec générée et validée
### Performance (v0.931)
- Pagination cursor-based : tracks, messages, feed social
- Profiling P50/P95/P99 documenté
### Consolidation (v0.941v0.943)
- Nettoyage code mort, migrations dédupliquées
- Schéma consolidé (000_full_schema.sql)
- Refactoring fichiers > 1000 lignes
### Hardening (v0.951v0.952)
- Load tests : 500 req/s API, 1000 WebSocket, 50 uploads
- Dashboard Grafana, alertes Prometheus
- Health check deep (DB, Redis, S3, RabbitMQ)
### Documentation & Ops (v0.961v0.962)
- Runbooks : déploiement, rollback, incident, rotation secrets, graceful degradation Redis
- API Reference, guide onboarding < 30 min
### Features cleanup (v0.971)
- Feature flag WebRTC_CALLS avec badge "Beta"
- Gamification fantôme supprimée
- docs/V1_LIMITATIONS.md, docs/API_VERSIONING_POLICY.md
### Beta & Polish (v0.981v0.982)
- Bug bash complet (Auth, Commerce, Média, Social)
- Lighthouse ≥ 90 (Performance, Accessibility)
- PWA offline vérifiée
- RGPD/CCPA : export, suppression, opt-out documentés
---
## Corrections de sécurité majeures
| ID | Description | Version |
|----|-------------|---------|
| VEZA-SEC-001 | OAuth generateJWT invalide | v0.901 |
| VEZA-SEC-002 | PasswordService.GenerateJWT sans contrôles | v0.901 |
| VEZA-SEC-005 | Webhook Hyperswitch vérification optionnelle | v0.901 |
| VEZA-SEC-006 | TokenBlacklist déconnecté du middleware | v0.901 |
| VEZA-SEC-007 | waveform_service sans ValidateExecPath | v0.901 |
| VEZA-SEC-003 | PKCE OAuth | v0.902 |
| VEZA-SEC-004 | Tokens OAuth chiffrés au repos | v0.902 |
| VEZA-SEC-008 | ORDER BY dynamique (whitelist) | v0.903 |
| VEZA-SEC-009 | Login/register exclus du rate limiter | v0.903 |
---
## Améliorations
- **Performance** : Pagination cursor-based, P99 < 500ms cible
- **Observabilité** : Request ID propagé, métriques Prometheus, Dashboard Grafana
- **RGPD/CCPA** : Export données, suppression compte, opt-out documentés et vérifiés
- **Accessibilité** : Lighthouse Accessibility ≥ 90, PWA mode offline
- **Documentation** : Runbooks opérationnels, checklist V1_SIGNOFF
---
## Breaking changes
### Pagination
- `GET /tracks`, `GET /conversations/:id/history`, `GET /social/feed` : support de `cursor` et `limit` en plus de `page`/`limit`. La pagination OFFSET reste en fallback pour rétro-compatibilité.
### API
- Aucun breaking change sur les signatures de réponse. Voir [docs/API_VERSIONING_POLICY.md](docs/API_VERSIONING_POLICY.md).
---
## Migration guide (v0.803 → v1.0.0)
1. **Variables d'environnement** : Vérifier `OAUTH_ENCRYPTION_KEY`, `CHAT_JWT_SECRET` séparé de `JWT_SECRET` en production, `HYPERSWITCH_WEBHOOK_SECRET` obligatoire.
2. **Migrations** : Appliquer les migrations depuis la dernière version. Si base existante post-consolidation (v0.942), le marqueur `000_mark_consolidated.sql` peut être requis. Voir [docs/MIGRATION_CONSOLIDATION.md](docs/MIGRATION_CONSOLIDATION.md).
3. **Tokens OAuth** : Si des tokens OAuth existants sont en clair, exécuter le script `cmd/tools/encrypt_oauth_tokens` avant mise à jour.
4. **Frontend** : Aucune action spécifique. Les curseurs de pagination sont optionnels.
---
## Limitations connues (v1.0)
Voir [docs/V1_LIMITATIONS.md](docs/V1_LIMITATIONS.md) pour la liste complète : WebRTC TURN/STUN (v1.1), 2FA SMS (v1.1), Redis HA (v1.1), etc.
---
## Liens
- [CHANGELOG.md](CHANGELOG.md) — Historique détaillé des versions
- [docs/ROADMAP_V09XX_TO_V1.md](docs/ROADMAP_V09XX_TO_V1.md) — Roadmap complète
- [docs/V1_SIGNOFF.md](docs/V1_SIGNOFF.md) — Checklist de validation

View file

@ -1 +1 @@
1.0.8
0.101.0

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
[
{
"path": "dist/assets/index-*.js",
"limit": "300 KB",
"gzip": true
},
{
"path": "dist/assets/*.css",
"limit": "80 KB",
"gzip": true
}
]

View file

@ -29,8 +29,7 @@ const queryClient = new QueryClient({
});
export const StorybookDecorator: Decorator = (Story, context) => {
const bgValue = context.globals?.backgrounds?.value;
const isDark = bgValue !== 'light'; // only 'light' triggers light mode, everything else = dark
const isDark = context.globals?.backgrounds?.value !== '#ffffff';
const initialEntries =
(context.parameters?.router as { initialEntries?: string[] } | undefined)?.initialEntries ?? ['/'];

View file

@ -1,11 +1,7 @@
// This file has been automatically migrated to valid ESM format by Storybook.
import { createRequire } from "node:module";
import type { StorybookConfig } from '@storybook/react-vite';
import { dirname, join } from "path"
const require = createRequire(import.meta.url);
function getAbsolutePath(value: string) {
return dirname(require.resolve(join(value, "package.json")))
}
@ -16,14 +12,16 @@ const config: StorybookConfig = {
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath("@storybook/addon-docs"),
getAbsolutePath("@storybook/addon-mcp"),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('msw-storybook-addon'),
],
"staticDirs": ['../public'],
"framework": getAbsolutePath('@storybook/react-vite'),
"docs": {
defaultName: "Documentation"
"defaultName": "Documentation",
"autodocs": true
},
"typescript": {
"reactDocgen": "react-docgen-typescript",

View file

@ -56,33 +56,27 @@ const preview: Preview = {
expanded: true,
},
a11y: {
test: 'error-on-violation',
test: 'todo',
},
viewport: {
options: customViewports,
viewports: customViewports,
},
backgrounds: {
options: {
dark: { name: 'dark', value: '#121215' },
light: { name: 'light', value: '#faf9f6' },
raised: { name: 'raised', value: '#1a1a1f' }
}
default: 'dark',
values: [
{ name: 'dark', value: '#121215' },
{ name: 'light', value: '#faf9f6' },
{ name: 'raised', value: '#1a1a1f' },
],
},
layout: 'centered',
docs: {
toc: true, // Enable table of contents in docs
},
},
decorators: [StorybookDecorator],
tags: ['autodocs'],
loaders: [mswLoader],
initialGlobals: {
backgrounds: {
value: 'dark'
}
}
};
export default preview;

View file

@ -207,7 +207,7 @@ La page `/library` utilise actuellement des données mockées ou des appels API
### FRONT-004: WebSocket Chat Partiellement Connecté
**Description:**
Le système de chat WebSocket est implémenté mais le serveur chat Rust a été retiré du projet. Une alternative (stream-server ou backend) devra être mise en place si le chat temps réel est requis.
Le système de chat WebSocket est implémenté mais peut ne pas être complètement connecté au serveur Rust (`veza-chat-server`).
**Fichiers Concernés:**
- `src/features/chat/hooks/useChat.ts` - Hook de connexion WebSocket
@ -224,7 +224,7 @@ Le système de chat WebSocket est implémenté mais le serveur chat Rust a été
- Nécessite validation avec le serveur Rust
**Recommandation:**
- Définir une stratégie de chat (backend ou stream-server)
- Tester la connexion avec `veza-chat-server` en développement
- Valider le protocole de messages WebSocket
- Implémenter des tests d'intégration E2E pour le chat
@ -284,16 +284,16 @@ Le système de streaming audio est implémenté côté frontend mais n'est pas c
---
#### 1.2. Connecter le WebSocket Chat
**Objectif:** Définir et implémenter une stratégie de chat temps réel (le serveur chat Rust a été retiré)
**Objectif:** Valider et finaliser la connexion au `veza-chat-server`
**Tâches:**
- Choisir une approche (backend WebSocket, stream-server, ou service tiers)
- Tester la connexion WebSocket avec le serveur Rust
- Valider le format des messages (JSON)
- Implémenter la gestion des erreurs de connexion
- Ajouter des indicateurs visuels de statut (connecté/déconnecté)
- Tests E2E pour le chat en temps réel
**Estimation:** 4-6 heures
**Dépendances:** Service de chat à définir
**Dépendances:** `veza-chat-server` opérationnel et accessible
---

Some files were not shown because too many files have changed in this diff Show more