ci: add release-gate pipeline, fix CI duplication, add OSS playbook docs

- Add release-gate.yml: 11-job pipeline (lint, tests, Docker build/smoke,
  Trivy scan, dep audit, audit checks, e2e API+UI, PR summary comment)
- Fix CI double-trigger on release branches (push + PR)
- Add CODE_OF_CONDUCT.md, SECURITY.md, PR template
- Add docs: git-workflow, architecture (ADR), release, operations, community
- Add profiles/fastapi-vue for automated audit checks
- Add docs/PROCESSES.md: index of 19 available processes
This commit is contained in:
Pier-Jean Malandrino 2026-04-09 13:20:43 +02:00
parent 6d3453dc7f
commit 941028e705
22 changed files with 2450 additions and 1 deletions

32
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,32 @@
## Type
- [ ] Feature (`feature/*`)
- [ ] Bug fix (`fix/*`)
- [ ] Hotfix (`hotfix/*`)
- [ ] Documentation
- [ ] Refactoring
- [ ] CI/CD
- [ ] Other: ___
## Summary
<!-- 1-3 sentences: what changed and why -->
## Related issues
<!-- Closes #123, Fixes #456 -->
## Checklist
- [ ] Branch follows naming convention (`feature/`, `fix/`, `hotfix/`)
- [ ] Commits follow [Conventional Commits](docs/git-workflow/commit-conventions.md)
- [ ] Tests added/updated for the change
- [ ] All tests pass (`pytest tests/ -v` + `npm run test:run`)
- [ ] Linting passes (`ruff check .` + `npx eslint src/`)
- [ ] `CHANGELOG.md` updated under `[Unreleased]`
- [ ] Documentation updated if behavior changed
- [ ] No secrets or credentials committed
## Screenshots / Evidence
<!-- If UI change: before/after screenshots. If API change: curl example or test output. -->

View file

@ -2,7 +2,7 @@ name: CI
on: on:
push: push:
branches: [main, 'release/**'] branches: [main]
pull_request: pull_request:
branches: [main, 'release/**'] branches: [main, 'release/**']

776
.github/workflows/release-gate.yml vendored Normal file
View file

@ -0,0 +1,776 @@
name: Release Gate
on:
push:
branches: ['release/**']
pull_request:
branches: [main]
# Only run on PRs from release branches
types: [opened, synchronize, reopened]
concurrency:
group: release-gate-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ──────────────────────────────────────────────────────────────────────────
# Phase 1 — Parallel validation (no Docker needed)
# ──────────────────────────────────────────────────────────────────────────
lint-typecheck:
name: Lint & type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: document-parser/requirements.txt
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install backend deps
run: |
pip install --upgrade pip
pip install -r document-parser/requirements.txt
pip install ruff
- name: Install frontend deps
run: cd frontend && npm ci
- name: Backend lint
run: cd document-parser && ruff check .
- name: Frontend lint
run: cd frontend && npx eslint src/
- name: Frontend type-check
run: cd frontend && npm run type-check
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: document-parser/requirements.txt
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install system deps
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends poppler-utils
- name: Backend tests
run: |
cd document-parser
pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx ruff
pytest tests/ -v --tb=short 2>&1 | tee /tmp/backend-tests.txt
- name: Frontend tests
run: |
cd frontend
npm ci
npm run test:run 2>&1 | tee /tmp/frontend-tests.txt
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: /tmp/*-tests.txt
dep-audit:
name: Dependency audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: document-parser/requirements.txt
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install deps
run: |
cd document-parser && pip install --upgrade pip && pip install -r requirements.txt && pip install pip-audit
cd ../frontend && npm ci
- name: Python audit
id: pip_audit
continue-on-error: true
run: |
cd document-parser
pip-audit --format=json --output=/tmp/pip-audit.json 2>&1 || true
pip-audit 2>&1 | tee /tmp/pip-audit.txt
# Check for critical vulnerabilities
if pip-audit --format=json 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
vulns = data.get('dependencies', [])
crits = [v for dep in vulns for v in dep.get('vulns', []) if 'CRITICAL' in v.get('fix_versions', [''])]
sys.exit(1 if crits else 0)
" 2>/dev/null; then
echo "critical=false" >> "$GITHUB_OUTPUT"
else
echo "critical=true" >> "$GITHUB_OUTPUT"
fi
- name: Node audit
id: npm_audit
continue-on-error: true
run: |
cd frontend
npm audit --json > /tmp/npm-audit.json 2>&1 || true
npm audit 2>&1 | tee /tmp/npm-audit.txt
# Check for critical vulnerabilities
CRITICAL_COUNT=$(npm audit --json 2>/dev/null | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
print(data.get('metadata', {}).get('vulnerabilities', {}).get('critical', 0))
except: print(0)
" 2>/dev/null || echo "0")
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "critical=true" >> "$GITHUB_OUTPUT"
else
echo "critical=false" >> "$GITHUB_OUTPUT"
fi
- name: Upload audit results
if: always()
uses: actions/upload-artifact@v4
with:
name: dep-audit-results
path: /tmp/*-audit.*
- name: Fail on critical vulnerabilities
if: steps.pip_audit.outputs.critical == 'true' || steps.npm_audit.outputs.critical == 'true'
run: |
echo "::error::Critical vulnerabilities found in dependencies"
exit 1
audit-checks:
name: Audit checks (commands.sh)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run automated audit checks
id: audit
run: |
bash profiles/fastapi-vue/commands.sh 2>&1 | tee /tmp/audit-checks.txt
continue-on-error: true
- name: Parse audit results
id: results
if: always()
run: |
PASS=$(grep -c "PASS" /tmp/audit-checks.txt || echo "0")
WARN=$(grep -c "WARN" /tmp/audit-checks.txt || echo "0")
FAIL=$(grep -c "FAIL" /tmp/audit-checks.txt || echo "0")
echo "pass=$PASS" >> "$GITHUB_OUTPUT"
echo "warn=$WARN" >> "$GITHUB_OUTPUT"
echo "fail=$FAIL" >> "$GITHUB_OUTPUT"
- name: Upload audit results
if: always()
uses: actions/upload-artifact@v4
with:
name: audit-checks-results
path: /tmp/audit-checks.txt
- name: Fail on audit failures
if: steps.audit.outcome == 'failure'
run: |
echo "::error::Audit checks failed — review profiles/fastapi-vue/commands.sh output"
exit 1
# ──────────────────────────────────────────────────────────────────────────
# Phase 2 — Docker build & validation
# ──────────────────────────────────────────────────────────────────────────
docker-build:
name: Docker build — ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
matrix:
target: [remote, local]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Extract version from branch
id: version
run: |
# release/1.2.3 → 1.2.3
BRANCH="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
VERSION=$(echo "$BRANCH" | sed 's|release/||')
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
target: ${{ matrix.target }}
load: true
tags: docling-studio:${{ matrix.target }}
build-args: APP_VERSION=${{ steps.version.outputs.value }}
cache-from: type=gha,scope=${{ matrix.target }}
cache-to: type=gha,mode=max,scope=${{ matrix.target }}
- name: Save image
run: |
docker save docling-studio:${{ matrix.target }} | gzip > /tmp/docling-studio-${{ matrix.target }}.tar.gz
- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: docker-image-${{ matrix.target }}
path: /tmp/docling-studio-${{ matrix.target }}.tar.gz
retention-days: 1
- name: Record image size
run: |
SIZE_BYTES=$(docker image inspect docling-studio:${{ matrix.target }} --format='{{.Size}}')
SIZE_MB=$((SIZE_BYTES / 1024 / 1024))
echo "$SIZE_MB" > /tmp/image-size-${{ matrix.target }}.txt
echo "Image size (${{ matrix.target }}): ${SIZE_MB} MB"
- name: Upload size artifact
uses: actions/upload-artifact@v4
with:
name: image-size-${{ matrix.target }}
path: /tmp/image-size-${{ matrix.target }}.txt
docker-smoke:
name: Docker smoke test
needs: [docker-build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download remote image
uses: actions/download-artifact@v4
with:
name: docker-image-remote
path: /tmp
- name: Load remote image
run: docker load < /tmp/docling-studio-remote.tar.gz
- name: Smoke test — remote
run: |
docker run -d --name smoke-remote -p 3000:3000 \
-e RATE_LIMIT_RPM=0 \
docling-studio:remote
echo "Waiting for container to start..."
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/api/health > /dev/null 2>&1; then
echo "Remote image healthy!"
curl -s http://localhost:3000/api/health | python3 -m json.tool
break
fi
if [ "$i" -eq 30 ]; then
echo "::error::Remote image failed health check"
docker logs smoke-remote
exit 1
fi
sleep 2
done
# Verify response fields
HEALTH=$(curl -s http://localhost:3000/api/health)
ENGINE=$(echo "$HEALTH" | python3 -c "import sys,json; print(json.load(sys.stdin)['engine'])")
STATUS=$(echo "$HEALTH" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
if [ "$STATUS" != "ok" ]; then
echo "::error::Health status is '$STATUS', expected 'ok'"
exit 1
fi
if [ "$ENGINE" != "remote" ]; then
echo "::error::Engine is '$ENGINE', expected 'remote'"
exit 1
fi
echo "Remote smoke test passed"
docker stop smoke-remote && docker rm smoke-remote
image-scan:
name: Security scan — ${{ matrix.target }}
needs: [docker-build]
runs-on: ubuntu-latest
strategy:
matrix:
target: [remote, local]
steps:
- uses: actions/checkout@v4
- name: Download image
uses: actions/download-artifact@v4
with:
name: docker-image-${{ matrix.target }}
path: /tmp
- name: Load image
run: docker load < /tmp/docling-studio-${{ matrix.target }}.tar.gz
- name: Run Trivy — CRITICAL (blocking)
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: docling-studio:${{ matrix.target }}
format: table
exit-code: 1
severity: CRITICAL
output: /tmp/trivy-critical-${{ matrix.target }}.txt
- name: Run Trivy — HIGH (informational)
if: always()
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: docling-studio:${{ matrix.target }}
format: table
exit-code: 0
severity: HIGH
output: /tmp/trivy-high-${{ matrix.target }}.txt
- name: Annotate HIGH vulnerabilities
if: always()
run: |
HIGH_COUNT=$(grep -c "HIGH" /tmp/trivy-high-${{ matrix.target }}.txt 2>/dev/null || echo "0")
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "::warning::${{ matrix.target }} image has $HIGH_COUNT HIGH severity vulnerabilities — review Trivy report"
else
echo "No HIGH vulnerabilities found in ${{ matrix.target }} image"
fi
- name: Upload Trivy reports
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-${{ matrix.target }}
path: /tmp/trivy-*-${{ matrix.target }}.txt
image-size:
name: Image size check
needs: [docker-build]
runs-on: ubuntu-latest
permissions:
packages: read
steps:
- uses: actions/checkout@v4
- name: Download size artifacts
uses: actions/download-artifact@v4
with:
pattern: image-size-*
merge-multiple: true
path: /tmp/sizes
- name: Get previous release sizes
id: prev
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Find the latest release tag
PREV_TAG=$(gh release list --repo "${{ github.repository }}" --limit 1 --json tagName -q '.[0].tagName' 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "No previous release found — skipping delta check"
echo "remote=0" >> "$GITHUB_OUTPUT"
echo "local=0" >> "$GITHUB_OUTPUT"
echo "found=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Previous release: $PREV_TAG"
echo "found=true" >> "$GITHUB_OUTPUT"
# Pull previous images and get sizes
for target in remote local; do
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${PREV_TAG#v}-${target}"
if docker pull "$IMAGE" 2>/dev/null; then
PREV_SIZE=$(docker image inspect "$IMAGE" --format='{{.Size}}')
PREV_MB=$((PREV_SIZE / 1024 / 1024))
echo "${target}=${PREV_MB}" >> "$GITHUB_OUTPUT"
echo "Previous $target size: ${PREV_MB} MB"
else
echo "Could not pull $IMAGE — skipping $target delta"
echo "${target}=0" >> "$GITHUB_OUTPUT"
fi
done
- name: Compare sizes
id: delta
run: |
for target in remote local; do
CURRENT=$(cat /tmp/sizes/image-size-${target}.txt 2>/dev/null || echo "0")
if [ "$target" = "remote" ]; then
PREV="${{ steps.prev.outputs.remote }}"
else
PREV="${{ steps.prev.outputs.local }}"
fi
if [ "$PREV" -gt 0 ] 2>/dev/null; then
DELTA=$((CURRENT - PREV))
DELTA_PCT=$((DELTA * 100 / PREV))
echo "${target}_current=${CURRENT}" >> "$GITHUB_OUTPUT"
echo "${target}_prev=${PREV}" >> "$GITHUB_OUTPUT"
echo "${target}_delta=${DELTA}" >> "$GITHUB_OUTPUT"
echo "${target}_delta_pct=${DELTA_PCT}" >> "$GITHUB_OUTPUT"
echo "$target: ${CURRENT}MB (was ${PREV}MB, delta: ${DELTA_PCT}%)"
if [ "$DELTA_PCT" -gt 10 ]; then
echo "::warning::$target image grew by ${DELTA_PCT}% (${PREV}MB → ${CURRENT}MB)"
elif [ "$DELTA_PCT" -lt -10 ]; then
echo "::notice::$target image shrank by ${DELTA_PCT#-}% (${PREV}MB → ${CURRENT}MB)"
fi
else
echo "${target}_current=${CURRENT}" >> "$GITHUB_OUTPUT"
echo "${target}_prev=n/a" >> "$GITHUB_OUTPUT"
echo "${target}_delta=n/a" >> "$GITHUB_OUTPUT"
echo "${target}_delta_pct=n/a" >> "$GITHUB_OUTPUT"
echo "$target: ${CURRENT}MB (no previous baseline)"
fi
done
- name: Save size report
run: |
cat > /tmp/image-size-report.txt <<EOF
Image Size Report
=================
remote: ${{ steps.delta.outputs.remote_current }}MB (prev: ${{ steps.delta.outputs.remote_prev }}MB, delta: ${{ steps.delta.outputs.remote_delta_pct }}%)
local: ${{ steps.delta.outputs.local_current }}MB (prev: ${{ steps.delta.outputs.local_prev }}MB, delta: ${{ steps.delta.outputs.local_delta_pct }}%)
EOF
cat /tmp/image-size-report.txt
- name: Upload size report
uses: actions/upload-artifact@v4
with:
name: image-size-report
path: /tmp/image-size-report.txt
# ──────────────────────────────────────────────────────────────────────────
# Phase 3 — E2E tests on built images
# ──────────────────────────────────────────────────────────────────────────
e2e-api:
name: E2E API tests (full scope)
needs: [docker-smoke]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-java@v4
with:
java-version: "17"
distribution: temurin
cache: maven
- name: Generate test PDFs
run: |
pip install fpdf2 pypdfium2
python e2e/generate-test-data.py
- name: Start stack
run: docker compose up -d --wait --build
timeout-minutes: 10
env:
RATE_LIMIT_RPM: "0"
- name: Wait for health
run: |
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/api/health > /dev/null 2>&1; then
echo "Backend healthy"
exit 0
fi
echo "Waiting for backend... ($i/30)"
sleep 5
done
echo "Backend failed to start"
docker compose logs
exit 1
- name: Run E2E API tests (full scope)
run: |
mvn test -f e2e/api/pom.xml \
-DbaseUrl=http://localhost:3000 \
-Dkarate.options="--tags @smoke,@regression,@e2e"
- name: Upload Karate reports
if: always()
uses: actions/upload-artifact@v4
with:
name: karate-api-reports
path: e2e/api/target/karate-reports/
- name: Tear down
if: always()
run: docker compose down
e2e-ui:
name: E2E UI tests (@critical)
needs: [docker-smoke]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-java@v4
with:
java-version: "17"
distribution: temurin
cache: maven
- name: Install Chrome
uses: browser-actions/setup-chrome@v1
with:
chrome-version: stable
- name: Generate test PDFs
run: |
pip install fpdf2 pypdfium2
python e2e/generate-test-data.py
- name: Start stack
run: docker compose up -d --wait --build
timeout-minutes: 10
env:
RATE_LIMIT_RPM: "0"
- name: Wait for health
run: |
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/api/health > /dev/null 2>&1; then
echo "Backend healthy"
exit 0
fi
echo "Waiting for backend... ($i/30)"
sleep 5
done
echo "Backend failed to start"
docker compose logs
exit 1
- name: Run critical UI tests
run: >
mvn test -f e2e/ui/pom.xml
-DbaseUrl=http://localhost:3000
-DuiBaseUrl=http://localhost:3000
-Dkarate.options="--tags @critical"
- name: Upload Karate UI reports
if: always()
uses: actions/upload-artifact@v4
with:
name: karate-ui-reports
path: e2e/ui/target/karate-reports/
- name: Tear down
if: always()
run: docker compose down
# ──────────────────────────────────────────────────────────────────────────
# Phase 4 — Release summary (comment on PR)
# ──────────────────────────────────────────────────────────────────────────
release-summary:
name: Release summary
if: always() && github.event_name == 'pull_request'
needs:
- lint-typecheck
- unit-tests
- dep-audit
- audit-checks
- docker-build
- docker-smoke
- image-scan
- image-size
- e2e-api
- e2e-ui
runs-on: ubuntu-latest
permissions:
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: /tmp/artifacts
- name: Build summary comment
id: summary
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Job results from needs context
LINT="${{ needs.lint-typecheck.result }}"
TESTS="${{ needs.unit-tests.result }}"
DEP="${{ needs.dep-audit.result }}"
AUDIT="${{ needs.audit-checks.result }}"
BUILD="${{ needs.docker-build.result }}"
SMOKE="${{ needs.docker-smoke.result }}"
SCAN="${{ needs.image-scan.result }}"
SIZE="${{ needs.image-size.result }}"
E2E_API="${{ needs.e2e-api.result }}"
E2E_UI="${{ needs.e2e-ui.result }}"
# Determine verdict
BLOCKING_FAILED="false"
for result in "$LINT" "$TESTS" "$BUILD" "$SMOKE" "$SCAN" "$E2E_API" "$E2E_UI"; do
if [ "$result" = "failure" ]; then
BLOCKING_FAILED="true"
fi
done
if [ "$BLOCKING_FAILED" = "true" ]; then
VERDICT="NO-GO"
VERDICT_ICON="🔴"
elif [ "$DEP" = "failure" ] || [ "$AUDIT" = "failure" ]; then
VERDICT="GO CONDITIONAL"
VERDICT_ICON="🟡"
else
VERDICT="GO"
VERDICT_ICON="🟢"
fi
# Status emoji helper
status_icon() {
case "$1" in
success) echo "✅" ;;
failure) echo "❌" ;;
skipped) echo "⏭️" ;;
cancelled) echo "🚫" ;;
*) echo "❓" ;;
esac
}
# Read image sizes
REMOTE_SIZE=$(cat /tmp/artifacts/image-size-remote/image-size-remote.txt 2>/dev/null || echo "?")
LOCAL_SIZE=$(cat /tmp/artifacts/image-size-local/image-size-local.txt 2>/dev/null || echo "?")
# Read size report
SIZE_REPORT=$(cat /tmp/artifacts/image-size-report/image-size-report.txt 2>/dev/null || echo "No size data available")
# Read audit summary
AUDIT_PASS=$(grep -c "PASS" /tmp/artifacts/audit-checks-results/audit-checks.txt 2>/dev/null || echo "?")
AUDIT_WARN=$(grep -c "WARN" /tmp/artifacts/audit-checks-results/audit-checks.txt 2>/dev/null || echo "?")
AUDIT_FAIL=$(grep -c "FAIL" /tmp/artifacts/audit-checks-results/audit-checks.txt 2>/dev/null || echo "?")
# Build the comment
cat > /tmp/summary.md << ENDOFCOMMENT
## ${VERDICT_ICON} Release Gate — ${VERDICT}
### Validation Results
| Check | Status | Blocking |
|-------|--------|----------|
| Lint & type-check | $(status_icon "$LINT") | Yes |
| Unit tests | $(status_icon "$TESTS") | Yes |
| Dependency audit | $(status_icon "$DEP") | Yes (CRITICAL) |
| Audit checks | $(status_icon "$AUDIT") | Yes |
| Docker build | $(status_icon "$BUILD") | Yes |
| Docker smoke test | $(status_icon "$SMOKE") | Yes |
| Image security scan | $(status_icon "$SCAN") | Yes (CRITICAL) |
| Image size check | $(status_icon "$SIZE") | No |
| E2E API (full scope) | $(status_icon "$E2E_API") | Yes |
| E2E UI (@critical) | $(status_icon "$E2E_UI") | Yes |
### Audit Checks Summary
| PASS | WARN | FAIL |
|------|------|------|
| ${AUDIT_PASS} | ${AUDIT_WARN} | ${AUDIT_FAIL} |
### Docker Image Sizes
| Target | Size |
|--------|------|
| remote | ${REMOTE_SIZE} MB |
| local | ${LOCAL_SIZE} MB |
<details>
<summary>Size delta vs previous release</summary>
\`\`\`
${SIZE_REPORT}
\`\`\`
</details>
---
<sub>Generated by <b>release-gate.yml</b> — see <a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">workflow run</a> for full details</sub>
ENDOFCOMMENT
- name: Post or update PR comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
COMMENT_TAG="<!-- release-gate-summary -->"
# Check for existing comment
EXISTING_COMMENT_ID=$(gh api \
"repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--jq ".[] | select(.body | contains(\"$COMMENT_TAG\")) | .id" \
2>/dev/null | head -1)
BODY=$(cat /tmp/summary.md)
BODY="${COMMENT_TAG}
${BODY}"
if [ -n "$EXISTING_COMMENT_ID" ]; then
gh api \
"repos/${{ github.repository }}/issues/comments/${EXISTING_COMMENT_ID}" \
-X PATCH \
-f body="$BODY"
echo "Updated existing comment #${EXISTING_COMMENT_ID}"
else
gh api \
"repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
-f body="$BODY"
echo "Posted new comment"
fi

43
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,43 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers at **[INSERT CONTACT EMAIL]**.
All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.

49
SECURITY.md Normal file
View file

@ -0,0 +1,49 @@
# Security Policy
## Supported Versions
| Version | Supported |
|---------|-----------|
| 0.3.x | Yes |
| < 0.3 | No |
## Reporting a Vulnerability
**Do NOT open a public GitHub issue for security vulnerabilities.**
Instead, please report them privately:
1. **Email**: Send a detailed report to **[INSERT SECURITY EMAIL]**
2. **GitHub Security Advisory**: Use [GitHub's private vulnerability reporting](https://github.com/scub-france/Docling-Studio/security/advisories/new)
### What to include
- Description of the vulnerability
- Steps to reproduce
- Affected component(s): `document-parser/`, `frontend/`, `docker-compose.yml`, etc.
- Impact assessment (data exposure, denial of service, privilege escalation, etc.)
- Suggested fix (if any)
### Response timeline
| Step | SLA |
|------|-----|
| Acknowledgment | < 48 hours |
| Initial assessment | < 7 days |
| Fix developed | < 14 days (critical), < 30 days (other) |
| Public disclosure | After fix is released |
### Process
1. We acknowledge your report and assign a severity level
2. We develop a fix in a **private branch** (never pushed publicly before the advisory)
3. We release the fix and publish a GitHub Security Advisory
4. We credit the reporter (unless they prefer anonymity)
## Security Best Practices (for contributors)
- Never commit secrets, API keys, or credentials
- Never disable CORS or security middleware without review
- Validate all user input at the API boundary
- Keep dependencies up to date (`pip audit`, `npm audit`)
- Follow the [OWASP Top 10](https://owasp.org/www-project-top-ten/) guidelines

179
docs/PROCESSES.md Normal file
View file

@ -0,0 +1,179 @@
# Available Processes
Index of all documented processes in Docling Studio. Each process is a structured, repeatable workflow with a clear trigger and deliverable.
---
## Development
| # | Process | Trigger / Command | Doc | Output |
|---|---------|-------------------|-----|--------|
| 1 | **Commit** | Every commit | [commit-conventions.md](git-workflow/commit-conventions.md) | Conventional Commit message |
| 2 | **Code Review** | Every PR | [code-review-checklist.md](git-workflow/code-review-checklist.md) | Reviewed PR with checklist |
| 3 | **Merge** | PR approved + CI green | [merge-policy.md](git-workflow/merge-policy.md) | Squash/merge commit on target branch |
| 4 | **ADR** | Architecture decision needed | [adr-guide.md](architecture/adr-guide.md) + [adr-template.md](architecture/adr-template.md) | `docs/architecture/adrs/ADR-NNN-*.md` |
### How to invoke
```
# Ask for a code review
Review cette PR avec docs/git-workflow/code-review-checklist.md
# Create an ADR
Crée un ADR pour [la décision à documenter]
```
---
## Quality & Audit
| # | Process | Trigger / Command | Doc | Output |
|---|---------|-------------------|-----|--------|
| 5 | **Full Release Audit** | Before merging `release/*` to `main` | [docs/audit/master.md](audit/master.md) | 12 audit reports + summary in `reports/release-X.Y.Z/` |
| 6 | **Single Audit** | Targeted check on one axis | [docs/audit/audits/*.md](audit/audits/) | Single audit report |
| 7 | **Re-Audit** | After fixing CRIT/MAJ findings | [docs/audit/master.md](audit/master.md) | Updated report |
| 8 | **Automated Checks** | Quick validation before audit | [profiles/fastapi-vue/commands.sh](../profiles/fastapi-vue/commands.sh) | PASS/WARN/FAIL per check |
### How to invoke
```
# Full audit
Audite la branche release/X.Y.Z en suivant docs/audit/master.md
# Single audit
Exécute l'audit docs/audit/audits/08-security.md sur la branche courante
# Re-audit after fixes
Re-audite les écarts CRIT et MAJ du rapport docs/audit/reports/release-X.Y.Z/summary.md
# Automated checks (shell)
bash profiles/fastapi-vue/commands.sh
```
---
## Release & Deploy
| # | Process | Trigger / Command | Doc | Output |
|---|---------|-------------------|-----|--------|
| 9 | **Release Gate** | Auto on push/PR `release/*``main` | [release-gate.yml](../.github/workflows/release-gate.yml) | GO / GO CONDITIONAL / NO-GO comment on PR |
| 10 | **Release** | Feature freeze on `release/*` | [CONTRIBUTING.md](../CONTRIBUTING.md#release-process) | Tag `vX.Y.Z`, Docker images on ghcr.io |
| 11 | **Deployment** | After release tag | [deployment-checklist.md](release/deployment-checklist.md) | Running instance at new version |
| 12 | **Rollback** | Post-deploy failure detected | [rollback-playbook.md](release/rollback-playbook.md) | Reverted to last known good version |
| 13 | **Hotfix** | Critical bug on released version | [CONTRIBUTING.md](../CONTRIBUTING.md#hotfix) | Patch release `vX.Y.Z+1` |
### Release Gate details
The release gate runs **automatically** on every push to `release/**` and on PRs targeting `main`. It validates 10 checks in 4 phases:
```
Phase 1 (parallel) Phase 2 (Docker) Phase 3 (E2E) Phase 4
┌──────────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ lint+typecheck│ │ docker build │──────▶│ e2e API │───▶│ release │
│ unit tests │ │ docker smoke │──────▶│ e2e UI │ │ summary │
│ dep audit │ │ image scan │ └─────────────┘ │ (PR comment)│
│ audit checks │ │ image size │ └─────────────┘
└──────────────┘ └──────────────┘
```
| Check | Blocks merge? | Details |
|-------|---------------|---------|
| Lint & type-check | Yes | ruff + ESLint + vue-tsc |
| Unit tests | Yes | pytest + Vitest |
| Dep audit | Yes (CRITICAL) | pip-audit + npm audit |
| Audit checks | Yes | `profiles/fastapi-vue/commands.sh` |
| Docker build | Yes | Both targets (remote + local) |
| Docker smoke | Yes | Start container, verify `/api/health` |
| Image scan (Trivy) | Yes (CRITICAL) | HIGH = warning annotation |
| Image size | No (warning) | Delta vs previous release, alert if > 10% |
| E2E API | Yes | `@smoke,@regression,@e2e` (full scope) |
| E2E UI | Yes | `@critical` |
**Verdict**: posted as a comment on the release PR:
- **GO** — all checks pass
- **GO CONDITIONAL** — blocking checks pass, dep audit or audit checks have warnings
- **NO-GO** — at least one blocking check failed
### How to invoke
```
# Release gate runs automatically — no manual trigger needed
# Just push to release/* or open a PR release/* → main
# Prepare a release
Prépare la release X.Y.Z en suivant CONTRIBUTING.md#release-process
# Deploy
Déploie en suivant docs/release/deployment-checklist.md
# Rollback
Rollback en suivant docs/release/rollback-playbook.md
```
---
## Operations
| # | Process | Trigger / Command | Doc | Output |
|---|---------|-------------------|-----|--------|
| 14 | **Incident Response** | Service down or degraded | [incident-response.md](operations/incident-response.md) | Mitigation + post-mortem |
| 15 | **Security Vulnerability** | Vuln reported | [security-response.md](operations/security-response.md) | Fix + advisory |
| 16 | **Monitoring Setup** | New deployment | [monitoring-checklist.md](operations/monitoring-checklist.md) | Monitoring configured |
### How to invoke
```
# Incident
Gère l'incident SEV-1 en suivant docs/operations/incident-response.md
# Security vulnerability
Traite la vulnérabilité signalée en suivant docs/operations/security-response.md
```
---
## Community & Governance
| # | Process | Trigger / Command | Doc | Output |
|---|---------|-------------------|-----|--------|
| 17 | **Onboarding** | New contributor | [onboarding-guide.md](community/onboarding-guide.md) | First PR merged |
| 18 | **Issue Triage** | New issue opened | [issue-triage-process.md](community/issue-triage-process.md) | Labeled + prioritized issue |
| 19 | **Roadmap Update** | Release cycle planning | [roadmap-template.md](community/roadmap-template.md) | Updated roadmap |
### How to invoke
```
# Triage issues
Trie les issues ouvertes en suivant docs/community/issue-triage-process.md
# Update roadmap
Mets à jour la roadmap en suivant docs/community/roadmap-template.md
```
---
## Quick Reference
| Category | Processes | Key doc |
|----------|-----------|---------|
| **Dev** | Commit, Review, Merge, ADR | `docs/git-workflow/` |
| **Quality** | Audit (full/single/re-audit), Auto-checks | `docs/audit/master.md` |
| **Release** | Release, Deploy, Rollback, Hotfix | `docs/release/` + `CONTRIBUTING.md` |
| **Ops** | Incident, Security, Monitoring | `docs/operations/` |
| **Community** | Onboarding, Triage, Roadmap | `docs/community/` |
---
## Standards & References
These are not processes but reference documents used by the processes above:
| Document | Purpose |
|----------|---------|
| [CONTRIBUTING.md](../CONTRIBUTING.md) | Dev setup, branching, release, versioning |
| [coding-standards.md](architecture/coding-standards.md) | Naming, style, architecture rules |
| [e2e/CONVENTIONS.md](../e2e/CONVENTIONS.md) | Karate / Karate UI test conventions |
| [architecture.md](architecture.md) | System architecture (backend + frontend) |
| [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md) | Community behavior standards |
| [SECURITY.md](../SECURITY.md) | Vulnerability reporting policy |
| [profiles/fastapi-vue/profile.md](../profiles/fastapi-vue/profile.md) | Stack layer mapping for audits |

View file

@ -0,0 +1,52 @@
# Architecture Decision Records (ADR) — Guide
## What is an ADR?
An ADR is a short document that captures a significant architectural decision, along with the context and consequences. ADRs create a **decision log** so future contributors understand *why* the codebase looks the way it does.
## When to Write an ADR
Write an ADR when:
- Choosing or replacing a framework, library, or tool
- Changing the architecture (new layer, new pattern, new boundary)
- Making a trade-off that future developers might question
- Deciding NOT to do something (these are often the most valuable)
Do NOT write an ADR for:
- Implementation details that are obvious from the code
- Formatting or style choices (those go in [coding-standards.md](coding-standards.md))
- Bug fixes or minor refactors
## How to Write an ADR
1. Copy `adr-template.md` to `docs/architecture/adrs/ADR-NNN-short-title.md`
2. Number sequentially (ADR-001, ADR-002, ...)
3. Fill in all sections — especially **Context** (the *why*) and **Alternatives Considered**
4. Set status to `Proposed`
5. Open a PR for team review
6. Once merged, update status to `Accepted`
## Existing Decisions (captured retroactively)
These decisions were made before the ADR process was introduced. They are documented here for context:
| Decision | Rationale | Date |
|----------|-----------|------|
| Clean Architecture (hexagonal) for backend | Decouple domain from framework — enable converter swapping (local/remote) | 2025-01 |
| FastAPI over Django | Async-first, lightweight, Pydantic-native — better fit for a single-purpose API | 2025-01 |
| Vue 3 + Pinia over React | Composition API + built-in reactivity — smaller bundle for this use case | 2025-01 |
| SQLite over PostgreSQL | Single-file DB, zero ops — appropriate for a document processing tool | 2025-01 |
| Karate over Playwright for e2e | Team expertise, unified API+UI testing, JVM ecosystem | 2026-03 |
| Feature flags via `/api/health` | No external service needed — backend capabilities drive frontend UI | 2026-03 |
## Lifecycle
```
Proposed → Accepted → [Deprecated | Superseded by ADR-NNN]
```
- **Deprecated**: The decision is no longer relevant (feature removed, context changed)
- **Superseded**: A newer ADR replaces this one (link to the new ADR)
- Never delete an ADR — mark it as deprecated/superseded instead

View file

@ -0,0 +1,39 @@
# ADR-NNN: [Title]
**Date**: YYYY-MM-DD
**Status**: Proposed | Accepted | Deprecated | Superseded by ADR-NNN
**Deciders**: [names]
## Context
<!-- What is the issue we're seeing that is motivating this decision? What forces are at play? -->
## Decision
<!-- What is the change we're proposing and/or doing? -->
## Consequences
### Positive
- ...
### Negative
- ...
### Neutral
- ...
## Alternatives Considered
### Alternative 1: [Name]
- **Pros**: ...
- **Cons**: ...
- **Why rejected**: ...
## References
- [Link to issue, RFC, or discussion]

View file

@ -0,0 +1,88 @@
# Coding Standards
Conventions for writing consistent, readable code across the Docling Studio codebase.
## Python (Backend — `document-parser/`)
### Tooling
| Tool | Purpose | Config |
|------|---------|--------|
| **Ruff** | Linting + formatting | `ruff.toml` / `pyproject.toml` |
| **pytest** | Testing | `pytest.ini` / `pyproject.toml` |
| **mypy** (optional) | Type checking | — |
### Naming
| Element | Convention | Example |
|---------|-----------|---------|
| Modules | `snake_case` | `analysis_repo.py` |
| Classes | `PascalCase` | `AnalysisJob`, `DocumentConverter` |
| Functions / methods | `snake_case` | `create_analysis()` |
| Constants | `UPPER_SNAKE_CASE` | `MAX_CONCURRENT_ANALYSES` |
| Private | `_leading_underscore` | `_build_converter()` |
### Style Rules
- Max function length: **30 lines** (soft limit — justify longer ones)
- Max file length: **300 lines** (split into modules if exceeded)
- Imports: standard library → third-party → local, separated by blank lines
- Type hints on all public functions
- Docstrings only on non-obvious public APIs (don't state the obvious)
- No `# type: ignore` without a comment explaining why
### Architecture Rules
- **Domain layer** (`domain/`): zero imports from `api/`, `persistence/`, `infra/`
- **Persistence layer** (`persistence/`): only imports from `domain/`
- **API layer** (`api/`): never imports from `persistence/` directly — goes through `services/`
- **Services** (`services/`): orchestrate, don't implement — delegate to domain and infra
## TypeScript / Vue (Frontend — `frontend/src/`)
### Tooling
| Tool | Purpose | Config |
|------|---------|--------|
| **ESLint** | Linting | `.eslintrc.*` |
| **Prettier** | Formatting | `.prettierrc` |
| **vue-tsc** | Type checking | `tsconfig.json` |
| **Vitest** | Testing | `vitest.config.ts` |
### Naming
| Element | Convention | Example |
|---------|-----------|---------|
| Components | `PascalCase.vue` | `BboxOverlay.vue` |
| Composables | `useCamelCase.ts` | `usePagination.ts` |
| Stores | `camelCase.ts` | `analysisStore.ts` |
| Types / Interfaces | `PascalCase` | `AnalysisJob`, `BboxRect` |
| Constants | `UPPER_SNAKE_CASE` | `DEFAULT_PAGE_SIZE` |
| CSS classes | `kebab-case` | `.bbox-overlay` |
| `data-e2e` attributes | `kebab-case` | `data-e2e="upload-zone"` |
### Style Rules
- **Composition API** only (`<script setup lang="ts">`) — no Options API
- One component per file
- Props defined with `defineProps<T>()` (type-based, not runtime)
- Emits defined with `defineEmits<T>()`
- Pinia stores: one per feature, in the feature directory
- No global state outside Pinia
- API calls only in `api.ts` files (never in components or stores directly)
### API Contract
- Frontend sends/receives **camelCase** (Pydantic `alias_generator`)
- Backend uses **snake_case** internally
- `pages_json` is an exception — contains raw snake_case from `dataclasses.asdict()`
## Karate (E2E — `e2e/`)
See [e2e/CONVENTIONS.md](../../e2e/CONVENTIONS.md) for detailed rules.
Key points:
- Use `data-e2e` selectors, never CSS classes
- Use `retry()`/`waitFor()`, never `Thread.sleep()` or `delay()`
- Setup via API, verify via UI, cleanup via API
- Tag tests: `@critical`, `@ui`, `@smoke`, `@regression`, `@e2e`

View file

@ -0,0 +1,85 @@
# Issue Triage Process
How we classify, prioritize, and manage GitHub issues.
## Labels
### Type
| Label | Description |
|-------|-------------|
| `bug` | Something is broken |
| `feature` | New functionality |
| `enhancement` | Improvement to existing functionality |
| `docs` | Documentation only |
| `question` | Support / how-to question |
| `chore` | Tooling, CI, dependencies |
### Priority
| Label | Response SLA | Fix SLA | Description |
|-------|-------------|---------|-------------|
| `priority: P0` | Same day | < 3 days | Critical service down, data loss, security |
| `priority: P1` | < 3 days | < 2 weeks | High major feature broken, no workaround |
| `priority: P2` | < 1 week | Next release | Medium feature degraded, workaround exists |
| `priority: P3` | < 2 weeks | Backlog | Low minor, cosmetic, nice-to-have |
### Status
| Label | Description |
|-------|-------------|
| `needs-info` | Waiting for reporter to provide more details |
| `confirmed` | Bug reproduced or feature accepted |
| `good-first-issue` | Suitable for new contributors |
| `help-wanted` | Open for community contribution |
| `wont-fix` | Intentional behavior, out of scope, or won't be addressed |
| `duplicate` | Already tracked in another issue |
| `stale` | No activity for 30 days |
### Component
| Label | Maps to |
|-------|---------|
| `component: backend` | `document-parser/` |
| `component: frontend` | `frontend/` |
| `component: e2e` | `e2e/` |
| `component: docker` | Docker / docker-compose |
| `component: ci` | `.github/workflows/` |
## Triage Workflow
```
New issue
├─ Missing info? → label `needs-info`, comment asking for details
│ (auto-close after 14 days if no response)
├─ Duplicate? → label `duplicate`, link to original, close
├─ Out of scope? → label `wont-fix`, explain why, close
└─ Valid issue
├─ Add type label (bug / feature / enhancement / ...)
├─ Add component label
├─ Assess priority (P0 / P1 / P2 / P3)
├─ If simple → add `good-first-issue`
└─ Assign to milestone (if applicable)
```
## Stale Policy
| Condition | Action |
|-----------|--------|
| No activity for **30 days** | Bot labels `stale` + comment |
| No activity for **14 more days** | Bot closes the issue |
| Reporter responds | `stale` label removed, timer resets |
Issues labeled `priority: P0` or `priority: P1` are exempt from the stale policy.
## Response Expectations
- **Every issue gets a response** (even if it's "thanks, we'll look at this next week")
- Acknowledge within the SLA for the priority level
- If you can't fix it soon, say so — don't leave reporters hanging
- Close issues with a comment explaining the resolution

View file

@ -0,0 +1,120 @@
# Onboarding Guide — First Contribution
Welcome! This guide helps you go from zero to your first merged PR.
## Prerequisites
| Tool | Version | Check |
|------|---------|-------|
| Python | 3.12+ | `python --version` |
| Node.js | 20+ | `node --version` |
| Docker & Docker Compose | latest | `docker compose version` |
| Git | 2.x | `git --version` |
| Java (for e2e) | 17+ | `java --version` |
| Maven (for e2e) | 3.9+ | `mvn --version` |
## Step 1 — Fork & Clone
```bash
# Fork on GitHub, then:
git clone https://github.com/<your-username>/Docling-Studio.git
cd Docling-Studio
git remote add upstream https://github.com/scub-france/Docling-Studio.git
```
## Step 2 — Run the Stack
The fastest way to see the app running:
```bash
docker compose up -d --wait
open http://localhost:3000
```
## Step 3 — Set Up for Development
### Backend
```bash
cd document-parser
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # remote mode (lightweight)
pip install ruff pytest pytest-asyncio httpx
uvicorn main:app --reload --port 8000
```
### Frontend
```bash
cd frontend
npm install
npm run dev # http://localhost:5173
```
## Step 4 — Pick Your First Issue
Look for issues labeled:
- `good-first-issue` — small, well-scoped, mentored
- `help-wanted` — we need help but it may be larger
- `docs` — documentation improvements (great starting point)
## Step 5 — Create a Branch
```bash
git checkout main && git pull upstream main
git checkout -b feature/my-change # or fix/my-fix
```
Follow the [branching strategy](../../CONTRIBUTING.md#branching-strategy).
## Step 6 — Code
- Read the [architecture docs](../architecture.md) to understand the codebase
- Follow the [coding standards](../architecture/coding-standards.md)
- Write tests for your changes
## Step 7 — Verify
```bash
# Backend
cd document-parser
ruff check . && ruff format --check .
pytest tests/ -v
# Frontend
cd frontend
npm run type-check
npx eslint src/
npm run test:run
```
## Step 8 — Commit & Push
Follow [commit conventions](../git-workflow/commit-conventions.md):
```bash
git add <files>
git commit -m "feat(scope): short description"
git push origin feature/my-change
```
## Step 9 — Open a PR
- Target: `main` (or `release/*` for pre-release fixes)
- Fill in the [PR template](../../.github/PULL_REQUEST_TEMPLATE.md)
- Add a line in `CHANGELOG.md` under `[Unreleased]`
- Wait for CI to pass, then request a review
## What to Expect
- A maintainer will review your PR within **3 business days**
- You may get feedback — this is normal and helpful
- Once approved, a maintainer will merge your PR
- Your contribution appears in the next release's changelog
## Need Help?
- Open a [Discussion](https://github.com/scub-france/Docling-Studio/discussions) on GitHub
- Check existing issues for similar questions
- Read the [CONTRIBUTING guide](../../CONTRIBUTING.md) for detailed rules

View file

@ -0,0 +1,44 @@
# Roadmap — Docling Studio
Last updated: YYYY-MM-DD
## Now (current release cycle)
<!-- Features actively being worked on -->
| Feature | Issue | Status |
|---------|-------|--------|
| ... | #NNN | In progress |
## Next (next release cycle)
<!-- Planned for the next release, prioritized -->
| Feature | Issue | Priority |
|---------|-------|----------|
| ... | #NNN | P1 |
## Later (future, no timeline)
<!-- Ideas accepted but not yet scheduled -->
| Feature | Issue |
|---------|-------|
| ... | #NNN |
## Not Planned
<!-- Explicitly declined or out of scope — explaining why avoids repeat requests -->
| Feature | Reason |
|---------|--------|
| ... | ... |
---
## How to Propose a Feature
1. Check [existing issues](https://github.com/scub-france/Docling-Studio/issues) — it may already be tracked
2. Open a new issue using the **Feature** template
3. If it's a significant change, consider writing an [ADR](../architecture/adr-guide.md)
4. The maintainers will triage and prioritize (see [issue triage](issue-triage-process.md))

View file

@ -0,0 +1,54 @@
# Code Review Checklist
Use this checklist when reviewing a Pull Request. Not every item applies to every PR — skip what is irrelevant, but consciously skip it rather than miss it.
## Correctness
- [ ] The code does what the PR description says it does
- [ ] Edge cases are handled (empty input, missing data, concurrent access)
- [ ] Error paths return meaningful messages, not stack traces
- [ ] No regressions on existing behavior
## Architecture & Design
- [ ] Dependencies flow inward: `api → services → domain` (never reversed)
- [ ] Domain layer has no imports from `api/`, `persistence/`, `infra/`
- [ ] No business logic in API routes — delegated to services
- [ ] New abstractions are justified (no premature generalization)
- [ ] Pinia stores stay within their feature boundary
## Security
- [ ] User input is validated at the API boundary (Pydantic schemas)
- [ ] No secrets, API keys, or credentials in the code
- [ ] No `eval()`, `exec()`, or raw SQL
- [ ] File paths are sanitized (no path traversal)
- [ ] CORS configuration is unchanged or explicitly justified
## Tests
- [ ] New behavior has corresponding tests
- [ ] Tests are deterministic (no `sleep`, no random, no network)
- [ ] Test names describe the scenario, not the implementation
- [ ] E2E tests use `data-e2e` attributes, not CSS classes (see [e2e/CONVENTIONS.md](../../e2e/CONVENTIONS.md))
## Code Quality
- [ ] No dead code, no commented-out code
- [ ] No `TODO` or `FIXME` without a linked issue
- [ ] Functions are < 30 lines (or well-justified)
- [ ] Variable names are descriptive and consistent with existing code
- [ ] No duplicated logic that should be extracted
## Documentation
- [ ] `CHANGELOG.md` updated under `[Unreleased]` if user-facing change
- [ ] API changes reflected in Pydantic schemas (auto-documented)
- [ ] Breaking changes are flagged in the commit message
## Pragmatic Checks
- [ ] The PR does ONE thing (feature, fix, or refactor — not all at once)
- [ ] No unrelated formatting changes mixed in
- [ ] The diff is reviewable in < 15 minutes (split large PRs)
- [ ] CI passes (tests + lint + type-check + build)

View file

@ -0,0 +1,73 @@
# Commit Conventions
We follow [Conventional Commits](https://www.conventionalcommits.org/) to keep the git history readable and to enable automated changelog generation.
## Format
```
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
```
## Types
| Type | When to use | Example |
|------|-------------|---------|
| `feat` | New feature | `feat(chunking): add hierarchical chunker support` |
| `fix` | Bug fix | `fix(upload): handle empty PDF gracefully` |
| `docs` | Documentation only | `docs: update architecture diagram` |
| `style` | Formatting, no logic change | `style(api): fix ruff formatting warnings` |
| `refactor` | Code restructuring, no behavior change | `refactor(persistence): extract repository base class` |
| `test` | Adding or updating tests | `test(analysis): add rechunk edge case tests` |
| `chore` | Tooling, CI, dependencies | `chore: bump docling to 2.31` |
| `perf` | Performance improvement | `perf(bbox): batch coordinate normalization` |
| `ci` | CI/CD pipeline changes | `ci: add multi-arch Docker build` |
## Scopes (optional)
Use the feature or component name:
| Scope | Maps to |
|-------|---------|
| `api` | `document-parser/api/` |
| `domain` | `document-parser/domain/` |
| `persistence` | `document-parser/persistence/` |
| `infra` | `document-parser/infra/` |
| `upload` | Upload feature (front + back) |
| `analysis` | Analysis feature (front + back) |
| `chunking` | Chunking feature (front + back) |
| `bbox` | Bounding box pipeline |
| `e2e` | `e2e/` tests |
| `docker` | Dockerfile, docker-compose |
| `ci` | `.github/workflows/` |
## Rules
1. **Subject line** — imperative mood, lowercase, no period, max 72 characters
2. **Body** — explain *why*, not *what* (the diff shows what)
3. **Breaking changes** — add `BREAKING CHANGE:` in the footer or `!` after the type: `feat(api)!: rename /analyses to /jobs`
4. **Issue references** — use `Closes #123` or `Fixes #456` in the footer
## Examples
```
feat(chunking): add page filtering in Prepare mode
Users can now select which pages to include in chunking.
The filter is persisted in the analysis job metadata.
Closes #87
```
```
fix(upload): reject files exceeding MAX_FILE_SIZE_MB
Previously, oversized files were accepted and failed silently
during Docling conversion. Now the API returns 413 with a
clear error message.
Fixes #102
```

View file

@ -0,0 +1,53 @@
# Merge Policy
## Merge Requirements
Every PR must meet these conditions before merging:
1. **CI green** — all checks pass (tests, lint, type-check, build)
2. **At least 1 approval** from a maintainer
3. **No unresolved conversations** — all review comments addressed
4. **Branch up to date** with target branch (rebase or merge from target)
5. **CHANGELOG updated** — if the change is user-facing
## Merge Strategy
| Branch type | Merge method | Why |
|-------------|-------------|-----|
| `feature/*``main` | **Squash merge** | Clean history — one commit per feature |
| `fix/*``main` | **Squash merge** | Clean history — one commit per fix |
| `fix/*``release/*` | **Squash merge** | Same rationale |
| `release/*``main` | **Merge commit** | Preserves the release branch history |
| `hotfix/*``main` | **Squash merge** | One atomic fix |
### Squash merge commit message
When squashing, the final commit message should follow [Conventional Commits](commit-conventions.md):
```
feat(chunking): add page filtering in Prepare mode (#87)
```
The PR number is appended automatically by GitHub.
## Stale PR Policy
| Condition | Action |
|-----------|--------|
| No activity for **14 days** | Maintainer pings the author |
| No activity for **30 days** | PR labeled `stale` |
| No activity for **45 days** | PR closed with comment (can be reopened) |
Draft PRs are exempt from the stale policy.
## Conflict Resolution
- **Rebase preferred** over merge commits for feature/fix branches
- If conflicts are complex, merge from target branch into the PR branch
- Never force-push a branch that has active reviewers without warning them
## Branch Cleanup
- Feature and fix branches are **deleted after merge** (GitHub auto-delete enabled)
- Release branches are kept until the next minor release
- Tags are never deleted

View file

@ -0,0 +1,93 @@
# Incident Response
## Severity Levels
| Level | Description | Examples | Response time |
|-------|-------------|----------|---------------|
| **SEV-1** | Service down, data loss, security breach | App unreachable, DB corrupted, credentials leaked | Immediate |
| **SEV-2** | Major feature broken, degraded for all users | Upload fails, analysis crashes, blank pages | < 2 hours |
| **SEV-3** | Minor feature broken, workaround exists | Bbox overlay misaligned, locale missing a key | Next business day |
## Response Steps
### 1. Detect
- Health endpoint returns error (`/api/health`)
- User report via GitHub issue
- CI/CD pipeline failure on `main`
- Docker container crash loop
### 2. Assess
- Determine severity (SEV-1/2/3)
- Identify affected component: backend, frontend, Docker, CI
- Check recent deployments: `git log --oneline -10 main`
### 3. Communicate
- **SEV-1**: Notify all maintainers immediately
- **SEV-2**: Open a GitHub issue with `priority: P0` label
- **SEV-3**: Open a GitHub issue with `priority: P1` label
### 4. Mitigate
**Rollback first, investigate later.**
- If the issue appeared after a deploy → [rollback](../release/rollback-playbook.md)
- If the issue is in a specific endpoint → disable the route or return a maintenance response
- If the issue is in Docling itself → switch to remote mode (`CONVERSION_ENGINE=remote`)
### 5. Fix
- Create a `hotfix/*` branch from the last stable tag
- Fix the root cause, add a regression test
- Run the [release audit](../audit/master.md) on the fix
- Deploy via the [deployment checklist](../release/deployment-checklist.md)
### 6. Post-Mortem
Write a post-mortem for SEV-1 and SEV-2 incidents using this template:
```markdown
# Post-Mortem: [Incident Title]
**Date**: YYYY-MM-DD
**Severity**: SEV-X
**Duration**: Xh Xm
**Author**: [name]
## Timeline
| Time | Event |
|------|-------|
| HH:MM | Incident detected |
| HH:MM | Severity assessed |
| HH:MM | Mitigation applied |
| HH:MM | Root cause identified |
| HH:MM | Fix deployed |
| HH:MM | Incident resolved |
## Root Cause
[What actually broke and why]
## Impact
[Who was affected, for how long, what data was at risk]
## What Went Well
- ...
## What Went Wrong
- ...
## Action Items
| Action | Owner | Due |
|--------|-------|-----|
| ... | ... | ... |
```
Store post-mortems in `docs/operations/post-mortems/YYYY-MM-DD-short-title.md`.

View file

@ -0,0 +1,105 @@
# Monitoring Checklist
What to monitor in a Docling Studio deployment.
## Health Endpoint
The primary monitoring signal is the health endpoint:
```bash
curl -s http://localhost:3000/api/health
```
Expected response:
```json
{
"status": "ok",
"engine": "local",
"version": "0.3.0",
"deploymentMode": "self-hosted"
}
```
**Alert if**: status != "ok", endpoint unreachable, or response time > 5s.
## Four Golden Signals
### 1. Latency
| Endpoint | Expected | Alert threshold |
|----------|----------|-----------------|
| `GET /api/health` | < 100ms | > 1s |
| `POST /api/documents` (upload) | < 2s | > 10s |
| `POST /api/analyses` (create) | < 500ms (queuing only) | > 5s |
| `GET /api/analyses/:id` (results) | < 500ms | > 3s |
### 2. Traffic
| Metric | What to watch |
|--------|---------------|
| Requests per minute | Baseline for normal usage |
| Uploads per hour | Capacity planning |
| Concurrent analyses | Should stay <= `MAX_CONCURRENT_ANALYSES` |
### 3. Errors
| Signal | Alert threshold |
|--------|-----------------|
| HTTP 5xx rate | > 1% of requests |
| Analysis failure rate | > 10% of analyses |
| Rate limit hits (429) | Spike = possible abuse |
### 4. Saturation
| Resource | Check command | Alert threshold |
|----------|---------------|-----------------|
| CPU | `docker stats` | > 90% sustained |
| Memory | `docker stats` | > 85% (especially in local mode with PyTorch) |
| Disk (SQLite + uploads) | `du -sh data/` | > 80% of volume |
| Docker container restarts | `docker inspect --format='{{.RestartCount}}'` | > 0 |
## Docker Health Check
The `docker-compose.yml` includes a built-in health check:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
```
Docker will mark the container as `unhealthy` after 3 consecutive failures.
## Log Monitoring
### Backend logs (uvicorn)
```bash
docker compose logs -f backend
```
Watch for:
- `ERROR` or `CRITICAL` log levels
- `TimeoutError` from Docling processing
- `sqlite3.OperationalError` (DB issues)
- `429 Too Many Requests` spikes
### Frontend logs (nginx)
```bash
docker compose logs -f frontend
```
Watch for:
- `502 Bad Gateway` (backend down)
- `413 Request Entity Too Large` (file size limit)
## Recommended Setup
For production deployments, consider:
1. **Uptime monitor** — ping `/api/health` every 60s (UptimeRobot, Healthchecks.io)
2. **Log aggregation** — ship Docker logs to a central service
3. **Alerting** — notify on container restart, health check failure, or error spike

View file

@ -0,0 +1,74 @@
# Security Vulnerability Response
Process for handling reported security vulnerabilities. See also [SECURITY.md](../../SECURITY.md) for the public-facing policy.
## Response Timeline
| Step | SLA | Action |
|------|-----|--------|
| **Acknowledge** | < 48h | Confirm receipt to the reporter |
| **Assess** | < 7 days | Determine severity (Critical / High / Medium / Low) |
| **Fix** | < 14 days (Critical), < 30 days (other) | Develop and test the fix |
| **Release** | Same day as fix | Publish patched version |
| **Disclose** | After release | Publish GitHub Security Advisory |
## Severity Assessment
| Severity | Criteria | Example |
|----------|----------|---------|
| **Critical** | Remote exploitation, data breach, no auth required | SQL injection in upload endpoint |
| **High** | Significant impact, some conditions required | Path traversal on file download |
| **Medium** | Limited impact or requires authenticated access | XSS in analysis results display |
| **Low** | Minimal impact, theoretical only | Information disclosure in error messages |
## Fix Process
1. **Create a private branch** — never push vulnerability details to a public branch before the fix is released
2. **Develop the fix** — include a regression test
3. **Run the security audit**`docs/audit/audits/08-security.md`
4. **Review** — at least one maintainer must review the fix
5. **Release** — tag, build, deploy (see [deployment checklist](../release/deployment-checklist.md))
6. **Publish advisory** — GitHub Security Advisory with CVE if applicable
## Advisory Template
```markdown
# Security Advisory: [Title]
**Severity**: Critical | High | Medium | Low
**Affected versions**: < X.Y.Z
**Fixed in**: X.Y.Z
**CVE**: CVE-YYYY-NNNNN (if assigned)
## Description
[What the vulnerability is, without providing exploitation details]
## Impact
[What an attacker could do]
## Mitigation
[Upgrade to X.Y.Z or apply workaround]
## Credit
[Reporter name, unless they prefer anonymity]
```
## Dependency Vulnerabilities
Run regular dependency audits:
```bash
# Backend
cd document-parser && pip audit
# Frontend
cd frontend && npm audit
```
For known vulnerabilities in dependencies:
- **Critical/High**: Update immediately, release a patch version
- **Medium/Low**: Include in the next planned release

View file

@ -0,0 +1,70 @@
# Deployment Checklist
Checklist for deploying a new release of Docling Studio. Applies to both self-hosted and Hugging Face Space deployments.
## Pre-Deploy
- [ ] Release branch merged to `main` via PR
- [ ] Git tag `vX.Y.Z` created on `main`
- [ ] Release audit passed (score >= 80, 0 CRITICAL) — see [docs/audit/master.md](../audit/master.md)
- [ ] `CHANGELOG.md` section finalized with release date
- [ ] `frontend/package.json` version matches the tag
- [ ] All CI checks green on the tagged commit
- [ ] Docker images built and pushed to `ghcr.io`:
- `X.Y.Z-remote`, `X.Y.Z-local`
- `X.Y-remote`, `X.Y-local`
- `latest-remote`, `latest-local`
## Deploy — Self-Hosted (Docker Compose)
- [ ] Pull the new image:
```bash
docker compose pull
```
- [ ] Check environment variables (`.env` or `docker-compose.override.yml`):
- `CONVERSION_ENGINE` (local / remote)
- `RATE_LIMIT_RPM`
- `MAX_FILE_SIZE_MB`
- `MAX_CONCURRENT_ANALYSES`
- [ ] Start the stack:
```bash
docker compose up -d --wait
```
- [ ] Verify health endpoint:
```bash
curl -s http://localhost:3000/api/health | jq .
# Expected: {"status":"ok","engine":"...","version":"X.Y.Z","deploymentMode":"self-hosted"}
```
## Deploy — Hugging Face Space
- [ ] Upload to HF Space via `huggingface-cli`:
```bash
huggingface-cli upload <space-id> . . --repo-type space
```
- [ ] Set environment variables in HF Space settings
- [ ] Wait for build to complete in HF Space logs
- [ ] Verify the app loads and health endpoint returns correct version
## Post-Deploy Smoke Test
- [ ] Home page loads
- [ ] Upload a PDF — document appears in the list
- [ ] Run an analysis — completes without error
- [ ] View results — markdown, HTML, bbox overlays render correctly
- [ ] Download results
- [ ] If local mode: test chunking
- [ ] Check `/api/health` returns the new version
## Rollback Triggers
Rollback immediately if any of these occur:
| Trigger | Action |
|---------|--------|
| Health endpoint returns error or wrong version | Rollback |
| Upload or analysis fails on a previously working PDF | Rollback |
| Frontend shows blank page or JS errors | Rollback |
| Error rate > 5% in the first 15 minutes | Rollback |
For rollback procedure, see [rollback-playbook.md](rollback-playbook.md).

View file

@ -0,0 +1,83 @@
# Rollback Playbook
## Decision: Rollback vs Hotfix?
| Situation | Strategy |
|-----------|----------|
| Broken deploy, previous version was stable | **Rollback** |
| Bug found but previous version also had issues | **Hotfix** |
| Data migration was applied (DB schema changed) | **Hotfix** (rollback may lose data) |
| Security vulnerability in the new release | **Rollback** + hotfix in parallel |
## Rollback Procedure — Docker Compose
### 1. Identify the last known good version
```bash
# List available tags
docker image ls ghcr.io/scub-france/docling-studio --format '{{.Tag}}' | sort -V
# Or check GitHub releases
gh release list --repo scub-france/Docling-Studio
```
### 2. Pin the image to the previous version
Edit `docker-compose.yml` or `docker-compose.override.yml`:
```yaml
services:
backend:
image: ghcr.io/scub-france/docling-studio:0.2.0-remote # pin to last good
frontend:
image: ghcr.io/scub-france/docling-studio:0.2.0-remote
```
### 3. Restart
```bash
docker compose down
docker compose up -d --wait
```
### 4. Verify
```bash
curl -s http://localhost:3000/api/health | jq .
# Confirm version matches the rolled-back version
```
### 5. Communicate
- Notify the team that a rollback was performed
- Open an issue describing the failure
- Link the failed release and the rollback commit
## Rollback Procedure — Hugging Face Space
1. Use `git revert` on the HF Space repo to revert to the previous commit
2. Or re-upload the previous version: `huggingface-cli upload <space-id> . . --repo-type space --revision <previous-commit>`
3. Verify the app loads correctly
## Database Considerations
Docling Studio uses **SQLite** with a file-based database. Key points:
- **No schema migrations** are applied automatically — the schema is created on first run
- If a new release adds columns, rolling back may cause "unknown column" errors
- **Before deploying a release that changes the DB schema**: back up the SQLite file
```bash
# Backup before deploy
cp data/docling-studio.db data/docling-studio.db.bak-$(date +%Y%m%d)
# Restore after rollback
cp data/docling-studio.db.bak-YYYYMMDD data/docling-studio.db
```
## Post-Rollback
1. Keep the rollback in place until the root cause is identified
2. Fix the issue on a `hotfix/*` branch
3. Re-run the [release audit](../audit/master.md) on the fix
4. Re-deploy following the [deployment checklist](deployment-checklist.md)

267
profiles/fastapi-vue/commands.sh Executable file
View file

@ -0,0 +1,267 @@
#!/usr/bin/env bash
# ============================================================================
# Docling Studio — Automated Audit Checks (FastAPI + Vue 3 profile)
# ============================================================================
# Runs verification commands for each of the 12 release audits.
# Usage: bash profiles/fastapi-vue/commands.sh
# Run from the repository root.
# ============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
PASS=0
WARN=0
FAIL=0
pass() { echo -e " ${GREEN}PASS${NC} $1"; ((PASS++)); }
warn() { echo -e " ${YELLOW}WARN${NC} $1"; ((WARN++)); }
fail() { echo -e " ${RED}FAIL${NC} $1"; ((FAIL++)); }
# ── 01. Clean Architecture ─────────────────────────────────────────────────
echo ""
echo "== 01. Clean Architecture =="
# Domain must not import from api, persistence, infra
if grep -rn "from api\|from persistence\|from infra\|import fastapi\|import aiosqlite" document-parser/domain/ 2>/dev/null; then
fail "Domain layer imports forbidden modules"
else
pass "Domain layer has no forbidden imports"
fi
# API must not import from persistence directly
if grep -rn "from persistence" document-parser/api/ 2>/dev/null; then
fail "API layer imports directly from persistence"
else
pass "API layer does not import from persistence"
fi
# ── 02. DDD ────────────────────────────────────────────────────────────────
echo ""
echo "== 02. DDD =="
# Domain models exist
if [ -f document-parser/domain/models.py ] && [ -f document-parser/domain/ports.py ]; then
pass "Domain models and ports exist"
else
fail "Missing domain/models.py or domain/ports.py"
fi
# Value objects exist
if [ -f document-parser/domain/value_objects.py ]; then
pass "Value objects defined"
else
warn "No value_objects.py in domain"
fi
# ── 03. Clean Code ─────────────────────────────────────────────────────────
echo ""
echo "== 03. Clean Code =="
# Check for files > 300 lines (backend)
LARGE_FILES=$(find document-parser -name "*.py" ! -path "*/.venv/*" ! -path "*/__pycache__/*" ! -path "*/tests/*" -exec awk 'END{if(NR>300) print FILENAME": "NR" lines"}' {} \;)
if [ -n "$LARGE_FILES" ]; then
warn "Large Python files (>300 lines):"
echo "$LARGE_FILES"
else
pass "No Python files exceed 300 lines"
fi
# Check for files > 300 lines (frontend)
LARGE_VUE=$(find frontend/src -name "*.vue" -o -name "*.ts" | xargs awk 'END{if(NR>300) print FILENAME": "NR" lines"}' 2>/dev/null)
if [ -n "$LARGE_VUE" ]; then
warn "Large frontend files (>300 lines):"
echo "$LARGE_VUE"
else
pass "No frontend files exceed 300 lines"
fi
# ── 04. KISS ───────────────────────────────────────────────────────────────
echo ""
echo "== 04. KISS =="
# Check for overly complex patterns
if grep -rn "type:\s*ignore" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null; then
warn "Found type: ignore comments (review if justified)"
else
pass "No unjustified type: ignore"
fi
# ── 05. DRY ────────────────────────────────────────────────────────────────
echo ""
echo "== 05. DRY =="
# Check for magic numbers in backend
if grep -rn "[^a-zA-Z_][0-9]\{3,\}[^0-9]" document-parser/ --include="*.py" ! -path "*/.venv/*" ! -path "*/tests/*" 2>/dev/null | grep -v "port\|version\|status\|#\|MAX_\|DEFAULT_\|LIMIT_" | head -5; then
warn "Possible magic numbers found (review above)"
else
pass "No obvious magic numbers"
fi
# ── 06. SOLID ──────────────────────────────────────────────────────────────
echo ""
echo "== 06. SOLID =="
# Check that ports (interfaces) exist
if grep -l "Protocol\|ABC\|abstractmethod" document-parser/domain/ports.py 2>/dev/null; then
pass "Domain ports use Protocol/ABC (Dependency Inversion)"
else
fail "No abstract ports found in domain"
fi
# ── 07. Decoupling ─────────────────────────────────────────────────────────
echo ""
echo "== 07. Decoupling =="
# Frontend should not hardcode backend URLs (except in config)
if grep -rn "localhost:8000\|127.0.0.1:8000" frontend/src/ --include="*.ts" --include="*.vue" 2>/dev/null | grep -v "config\|env\|http.ts"; then
fail "Frontend hardcodes backend URL outside config"
else
pass "Frontend backend URL is configurable"
fi
# ── 08. Security ───────────────────────────────────────────────────────────
echo ""
echo "== 08. Security =="
# Check for hardcoded secrets
if grep -rni "password\s*=\s*['\"].\+['\"\|secret\s*=\s*['\"].\+['\"\|api_key\s*=\s*['\"].\+['\"]" document-parser/ --include="*.py" ! -path "*/.venv/*" ! -path "*/tests/*" 2>/dev/null; then
fail "Possible hardcoded secrets found"
else
pass "No hardcoded secrets detected"
fi
# Check for eval/exec
if grep -rn "\beval(\|exec(" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null; then
fail "eval() or exec() found in backend"
else
pass "No eval/exec in backend"
fi
# Check CORS configuration exists
if grep -rn "CORSMiddleware" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null > /dev/null; then
pass "CORS middleware is configured"
else
warn "No CORS middleware found"
fi
# ── 09. Tests ──────────────────────────────────────────────────────────────
echo ""
echo "== 09. Tests =="
# Backend tests exist
BACKEND_TESTS=$(find document-parser/tests -name "test_*.py" 2>/dev/null | wc -l)
if [ "$BACKEND_TESTS" -gt 0 ]; then
pass "Backend: $BACKEND_TESTS test files found"
else
fail "No backend test files found"
fi
# Frontend tests exist
FRONTEND_TESTS=$(find frontend/src -name "*.test.*" 2>/dev/null | wc -l)
if [ "$FRONTEND_TESTS" -gt 0 ]; then
pass "Frontend: $FRONTEND_TESTS test files found"
else
fail "No frontend test files found"
fi
# E2E tests exist
E2E_TESTS=$(find e2e -name "*.feature" 2>/dev/null | wc -l)
if [ "$E2E_TESTS" -gt 0 ]; then
pass "E2E: $E2E_TESTS feature files found"
else
warn "No e2e feature files found"
fi
# Check for skipped tests
if grep -rn "@skip\|@ignore\|xit(\|xdescribe(\|pytest.mark.skip" document-parser/tests/ frontend/src/ 2>/dev/null | grep -v "helpers"; then
warn "Skipped tests found (review if intentional)"
else
pass "No skipped tests"
fi
# ── 10. CI / Build ────────────────────────────────────────────────────────
echo ""
echo "== 10. CI / Build =="
# CI workflow exists
if [ -f .github/workflows/ci.yml ]; then
pass "CI workflow exists"
else
fail "No CI workflow found"
fi
# Dockerfile exists
if [ -f Dockerfile ]; then
pass "Dockerfile exists"
else
fail "No Dockerfile found"
fi
# Health check in docker-compose
if grep -q "healthcheck" docker-compose.yml 2>/dev/null; then
pass "Docker Compose has health check"
else
warn "No health check in docker-compose.yml"
fi
# ── 11. Documentation ─────────────────────────────────────────────────────
echo ""
echo "== 11. Documentation =="
# CHANGELOG exists and has content
if [ -f CHANGELOG.md ] && [ -s CHANGELOG.md ]; then
pass "CHANGELOG.md exists and is not empty"
else
fail "CHANGELOG.md missing or empty"
fi
# README exists
if [ -f README.md ]; then
pass "README.md exists"
else
fail "README.md missing"
fi
# Check for TODO/FIXME without issue reference
TODOS=$(grep -rn "TODO\|FIXME" document-parser/ frontend/src/ --include="*.py" --include="*.ts" --include="*.vue" ! -path "*/.venv/*" ! -path "*/node_modules/*" 2>/dev/null | grep -v "#[0-9]" | head -5)
if [ -n "$TODOS" ]; then
warn "TODO/FIXME without issue reference:"
echo "$TODOS"
else
pass "No orphaned TODO/FIXME"
fi
# ── 12. Performance ───────────────────────────────────────────────────────
echo ""
echo "== 12. Performance =="
# Check for synchronous file I/O in async context
if grep -rn "open(" document-parser/api/ document-parser/services/ --include="*.py" 2>/dev/null | grep -v "aiofiles\|async\|#"; then
warn "Synchronous file I/O in async code (review above)"
else
pass "No synchronous file I/O in async endpoints"
fi
# Check for N+1 patterns (loop with DB call)
if grep -rn "for.*in.*:" document-parser/services/ --include="*.py" -A5 2>/dev/null | grep "await.*repo\|await.*db"; then
warn "Possible N+1 query pattern (review above)"
else
pass "No obvious N+1 patterns"
fi
# ── Summary ────────────────────────────────────────────────────────────────
echo ""
echo "============================================"
echo -e " ${GREEN}PASS${NC}: $PASS"
echo -e " ${YELLOW}WARN${NC}: $WARN"
echo -e " ${RED}FAIL${NC}: $FAIL"
echo "============================================"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi

View file

@ -0,0 +1,70 @@
# Stack Profile — FastAPI + Vue 3
Profile for running the 12 release audits on Docling Studio.
## Layer Mapping
| Generic Layer | Docling Studio Path | Language |
|---------------|---------------------|----------|
| **Domain** | `document-parser/domain/` | Python |
| **Services** | `document-parser/services/` | Python |
| **API** | `document-parser/api/` | Python |
| **Infrastructure** | `document-parser/infra/` | Python |
| **Persistence** | `document-parser/persistence/` | Python |
| **Frontend** | `frontend/src/` | TypeScript / Vue |
| **Tests (backend)** | `document-parser/tests/` | Python |
| **Tests (frontend)** | `frontend/src/**/*.test.*` | TypeScript |
| **Tests (e2e API)** | `e2e/api/` | Karate (Gherkin) |
| **Tests (e2e UI)** | `e2e/ui/` | Karate UI (Gherkin) |
| **CI/CD** | `.github/workflows/` | YAML |
| **Docker** | `Dockerfile`, `docker-compose.yml`, `nginx.conf` | Docker / Nginx |
## Excluded Paths
These paths are excluded from audits:
- `document-parser/.venv/`
- `document-parser/__pycache__/`
- `frontend/node_modules/`
- `frontend/dist/`
- `e2e/**/target/`
## Framework Detection
Imports that should NOT appear in the domain layer:
```python
# Forbidden in document-parser/domain/
from fastapi import ...
from pydantic import ... # except BaseModel for value objects
import aiosqlite
from infra import ...
from persistence import ...
from api import ...
```
Imports that should NOT cross feature boundaries in the frontend:
```typescript
// features/analysis/ should NOT import from features/chunking/store
// features/document/ should NOT import from features/analysis/store
// Cross-feature communication goes through shared/ or events
```
## Tools & Commands
| Task | Command |
|------|---------|
| Backend lint | `cd document-parser && ruff check .` |
| Backend format | `cd document-parser && ruff format --check .` |
| Backend tests | `cd document-parser && pytest tests/ -v` |
| Frontend lint | `cd frontend && npx eslint src/` |
| Frontend type-check | `cd frontend && npm run type-check` |
| Frontend format | `cd frontend && npx prettier --check src/` |
| Frontend tests | `cd frontend && npm run test:run` |
| E2E API tests | `mvn test -f e2e/api/pom.xml -Dkarate.options="--tags @smoke"` |
| E2E UI tests | `mvn test -f e2e/ui/pom.xml -Dkarate.options="--tags @critical"` |
| Docker build | `docker compose build` |
| Docker health | `curl -s http://localhost:3000/api/health` |
| Dependency audit (Python) | `cd document-parser && pip audit` |
| Dependency audit (Node) | `cd frontend && npm audit` |