Skip to content

Commit b385980

Browse files
desioracclaude
andcommitted
feat(api): latest_proof_id in /v1/stats + Dockerfile + Azure deploy script
- /v1/stats retourne maintenant latest_proof_id (dernière preuve générée) - Dockerfile ajouté au repo (python:3.11-slim, uvicorn port 8100) - scripts/deploy_azure.sh : déploiement Azure Container Apps depuis local (gates VPS → rsync → az acr build → az containerapp update → health check) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5440316 commit b385980

3 files changed

Lines changed: 255 additions & 1 deletion

File tree

Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM python:3.11-slim
2+
3+
ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
4+
5+
WORKDIR /app
6+
7+
RUN apt-get update && apt-get install -y --no-install-recommends build-essential libpq-dev curl ca-certificates && rm -rf /var/lib/apt/lists/*
8+
9+
RUN pip install --no-cache-dir fastapi==0.136.1 uvicorn==0.46.0 httpx==0.28.1 httpx-sse==0.4.3 pydantic==2.13.4 pydantic-settings==2.14.0 psycopg2-binary==2.9.11 redis==7.2.0 cryptography==48.0.0 stripe python-multipart anyio requests==2.32.3 jinja2 asn1crypto pyasn1 rfc3161-client sigstore
10+
11+
COPY . /app/
12+
13+
RUN pip install --no-cache-dir -e . 2>/dev/null || pip install --no-cache-dir . 2>/dev/null || true
14+
15+
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 CMD curl -fsS http://127.0.0.1:8100/ || exit 1
16+
17+
EXPOSE 8100
18+
CMD ["uvicorn", "trust_layer.app:app", "--host", "0.0.0.0", "--port", "8100"]

scripts/deploy_azure.sh

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
#!/usr/bin/env bash
2+
# deploy_trust_layer_azure.sh — Deploy Trust Layer to Azure Container Apps
3+
#
4+
# Prérequis : az CLI loggé (az login), accès SSH VPS (clé ~/.ssh/vps_ovh)
5+
#
6+
# Usage:
7+
# ./scripts/deploy_trust_layer_azure.sh [--skip-gates] [--skip-smoke] [--minor|--major]
8+
#
9+
# Flags:
10+
# --skip-gates Bypass CI + tests (hotfix uniquement)
11+
# --skip-smoke Bypass smoke test post-deploy
12+
# --minor Bump minor (1.0.x → 1.1.0)
13+
# --major Bump major (1.x.x → 2.0.0)
14+
# (défaut) Bump patch (1.0.2 → 1.0.3)
15+
16+
set -euo pipefail
17+
18+
# ============================================================
19+
# CONFIGURATION
20+
# ============================================================
21+
VPS_HOST="ubuntu@57.131.27.61"
22+
VPS_SSH_KEY="$HOME/.ssh/vps_ovh"
23+
VPS_REPO="/opt/claude-ceo/workspace/arkforge-trust-layer"
24+
BUILD_CTX="/tmp/tl-build-ctx"
25+
26+
ACR="crarkforgeprod"
27+
ACR_IMAGE="crarkforgeprod.azurecr.io/trust-layer"
28+
CONTAINER_APP="ca-trust-layer-prod"
29+
RESOURCE_GROUP="rg-arkforge-prod"
30+
HEALTH_URL="https://trust.arkforge.tech/v1/health"
31+
32+
LOG_FILE="/tmp/deploy_trust_layer_azure.log"
33+
34+
# ============================================================
35+
# ARGS
36+
# ============================================================
37+
VERSION_BUMP="patch"
38+
SKIP_GATES=false
39+
SKIP_SMOKE=false
40+
for arg in "$@"; do
41+
case "$arg" in
42+
--minor) VERSION_BUMP="minor" ;;
43+
--major) VERSION_BUMP="major" ;;
44+
--skip-gates) SKIP_GATES=true ;;
45+
--skip-smoke) SKIP_SMOKE=true ;;
46+
esac
47+
done
48+
49+
# ============================================================
50+
# HELPERS
51+
# ============================================================
52+
log() { echo "[$(date -u +%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
53+
fail() { log "ERROR: $*"; exit 1; }
54+
55+
vps() { ssh -i "$VPS_SSH_KEY" -o ConnectTimeout=10 "$VPS_HOST" "$@"; }
56+
57+
bump_version() {
58+
local cur="$1" bump="$2"
59+
local maj min pat
60+
maj=$(echo "$cur" | cut -d. -f1 | tr -d 'v')
61+
min=$(echo "$cur" | cut -d. -f2)
62+
pat=$(echo "$cur" | cut -d. -f3)
63+
case "$bump" in
64+
major) echo "v$((maj+1)).0.0" ;;
65+
minor) echo "v${maj}.$((min+1)).0" ;;
66+
patch) echo "v${maj}.${min}.$((pat+1))" ;;
67+
esac
68+
}
69+
70+
mkdir -p "$(dirname "$LOG_FILE")"
71+
log "=== Trust Layer → Azure deploy — $(date -u) ==="
72+
log "Bump: $VERSION_BUMP | skip-gates: $SKIP_GATES | skip-smoke: $SKIP_SMOKE"
73+
74+
# ============================================================
75+
# PHASE 1 — GATES (s'exécutent sur le VPS)
76+
# ============================================================
77+
if [ "$SKIP_GATES" = false ]; then
78+
log "--- Phase 1: Gates ---"
79+
80+
log "Gate 1/3: proof-spec check_consistency..."
81+
if ! vps "python3 /opt/claude-ceo/workspace/proof-spec/check_consistency.py" >> "$LOG_FILE" 2>&1; then
82+
fail "Gate 1 FAILED — proof-spec check_consistency.py"
83+
fi
84+
log "Gate 1/3: OK"
85+
86+
log "Gate 2/3: agent-client tests..."
87+
if ! vps "cd /opt/claude-ceo/workspace/agent-client && python3 -m pytest tests/ -q --tb=short" >> "$LOG_FILE" 2>&1; then
88+
fail "Gate 2 FAILED — agent-client tests"
89+
fi
90+
log "Gate 2/3: OK"
91+
92+
log "Gate 3/3: trust-layer tests..."
93+
if ! vps "cd $VPS_REPO && python3 -m pytest tests/ -q --tb=short" >> "$LOG_FILE" 2>&1; then
94+
fail "Gate 3 FAILED — trust-layer tests"
95+
fi
96+
log "Gate 3/3: OK"
97+
98+
log "Toutes les gates OK"
99+
else
100+
log "Gates SKIPPED (--skip-gates)"
101+
fi
102+
103+
# ============================================================
104+
# PHASE 2 — VERSION BUMP SUR VPS
105+
# ============================================================
106+
log "--- Phase 2: Version bump ---"
107+
LAST_TAG=$(vps "git -C $VPS_REPO tag --sort=-v:refname | head -1" 2>/dev/null || echo "v0.0.0")
108+
[ -z "$LAST_TAG" ] && LAST_TAG="v0.0.0"
109+
NEW_TAG=$(bump_version "$LAST_TAG" "$VERSION_BUMP")
110+
NEW_VERSION="${NEW_TAG#v}"
111+
log "Version: $LAST_TAG$NEW_TAG"
112+
113+
PREV_COMMIT=$(vps "git -C $VPS_REPO rev-parse HEAD")
114+
115+
CURRENT_APP_VERSION=$(vps "grep -oP '(?<=__version__ = \")[^\"]+' $VPS_REPO/trust_layer/__init__.py 2>/dev/null || echo ''")
116+
if [ "$CURRENT_APP_VERSION" != "$NEW_VERSION" ]; then
117+
vps "sed -i 's/^__version__ = .*/__version__ = \"$NEW_VERSION\"/' $VPS_REPO/trust_layer/__init__.py"
118+
vps "cd $VPS_REPO && git add trust_layer/__init__.py && git commit -m 'chore: bump version to $NEW_VERSION'" >> "$LOG_FILE" 2>&1
119+
log "Version bumped: $CURRENT_APP_VERSION$NEW_VERSION"
120+
else
121+
log "Version déjà à $NEW_VERSION — pas de bump"
122+
fi
123+
124+
NEW_COMMIT=$(vps "git -C $VPS_REPO rev-parse HEAD")
125+
126+
# ============================================================
127+
# PHASE 3 — BUILD IMAGE AZURE (depuis local via az acr build)
128+
# ============================================================
129+
log "--- Phase 3: Build image ACR ---"
130+
131+
log "Rsync VPS → $BUILD_CTX..."
132+
rsync -az --delete \
133+
--exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \
134+
--exclude='.venv' --exclude='venv' --exclude='proofs' --exclude='data' \
135+
-e "ssh -i $VPS_SSH_KEY" \
136+
"${VPS_HOST}:${VPS_REPO}/" "$BUILD_CTX/"
137+
log "Rsync OK ($(du -sh "$BUILD_CTX" | cut -f1))"
138+
139+
IMAGE_TAG="${ACR_IMAGE}:${NEW_TAG}"
140+
log "Build: $IMAGE_TAG..."
141+
if ! az acr build \
142+
-r "$ACR" \
143+
-t "${ACR_IMAGE}:${NEW_TAG}" \
144+
-t "${ACR_IMAGE}:latest" \
145+
-f "$BUILD_CTX/Dockerfile" \
146+
"$BUILD_CTX/" >> "$LOG_FILE" 2>&1; then
147+
fail "Phase 3 FAILED — az acr build"
148+
fi
149+
log "Build OK → $IMAGE_TAG"
150+
151+
# ============================================================
152+
# PHASE 4 — DEPLOY CONTAINER APP
153+
# ============================================================
154+
log "--- Phase 4: Deploy Container App ---"
155+
156+
# Sauvegarder l'image courante pour rollback
157+
PREV_IMAGE=$(az containerapp show \
158+
-n "$CONTAINER_APP" -g "$RESOURCE_GROUP" \
159+
--query 'properties.template.containers[0].image' -o tsv 2>/dev/null)
160+
log "Image précédente (rollback): $PREV_IMAGE"
161+
162+
if ! az containerapp update \
163+
-n "$CONTAINER_APP" -g "$RESOURCE_GROUP" \
164+
--image "$IMAGE_TAG" \
165+
--revision-suffix "${NEW_TAG//./-}" >> "$LOG_FILE" 2>&1; then
166+
fail "Phase 4 FAILED — az containerapp update"
167+
fi
168+
log "Container App mis à jour → $IMAGE_TAG"
169+
170+
# ============================================================
171+
# PHASE 5 — HEALTH CHECK (8 × 10s = 80s max)
172+
# ============================================================
173+
log "--- Phase 5: Health check $HEALTH_URL ---"
174+
HEALTHY=false
175+
for i in $(seq 1 8); do
176+
sleep 10
177+
STATUS=$(curl -s --max-time 8 "$HEALTH_URL" | python3 -c "
178+
import sys, json
179+
try: print(json.load(sys.stdin).get('status','error'))
180+
except: print('error')
181+
" 2>/dev/null || echo "error")
182+
log "Attempt $i/8: status=$STATUS"
183+
if [ "$STATUS" = "ok" ]; then
184+
HEALTHY=true
185+
break
186+
fi
187+
done
188+
189+
if [ "$HEALTHY" = false ]; then
190+
log "Health check FAILED — rollback vers $PREV_IMAGE"
191+
az containerapp update \
192+
-n "$CONTAINER_APP" -g "$RESOURCE_GROUP" \
193+
--image "$PREV_IMAGE" >> "$LOG_FILE" 2>&1 && log "Rollback OK" || log "WARN: Rollback failed"
194+
fail "Health check FAILED après deploy — rolled back vers $PREV_IMAGE"
195+
fi
196+
log "Service healthy"
197+
198+
# ============================================================
199+
# PHASE 6 — SMOKE TEST (optionnel)
200+
# ============================================================
201+
if [ "$SKIP_SMOKE" = false ]; then
202+
log "--- Phase 6: Smoke test ---"
203+
SMOKE_SCRIPT="$BUILD_CTX/scripts/smoke_test_prod.py"
204+
if [ -f "$SMOKE_SCRIPT" ]; then
205+
SMOKE_BASE=$(echo "$HEALTH_URL" | sed 's|/v1/health||')
206+
if ! python3 "$SMOKE_SCRIPT" --base-url "$SMOKE_BASE" >> "$LOG_FILE" 2>&1; then
207+
log "Smoke test FAILED — rollback vers $PREV_IMAGE"
208+
az containerapp update \
209+
-n "$CONTAINER_APP" -g "$RESOURCE_GROUP" \
210+
--image "$PREV_IMAGE" >> "$LOG_FILE" 2>&1 && log "Rollback OK" || log "WARN: Rollback failed"
211+
fail "Smoke test FAILED — rolled back vers $PREV_IMAGE"
212+
fi
213+
log "Smoke test OK"
214+
else
215+
log "Smoke test script introuvable — skipped"
216+
fi
217+
else
218+
log "Smoke test SKIPPED (--skip-smoke)"
219+
fi
220+
221+
# ============================================================
222+
# PHASE 7 — TAG GIT + NOTIFICATION
223+
# ============================================================
224+
log "--- Phase 7: Release ---"
225+
vps "cd $VPS_REPO && git tag $NEW_TAG && git push origin main $NEW_TAG" >> "$LOG_FILE" 2>&1
226+
log "Tag $NEW_TAG pushé"
227+
228+
log "=== Deploy $NEW_TAG COMPLETE ==="
229+
echo ""
230+
echo " Trust Layer $NEW_TAG déployé sur Azure Container Apps"
231+
echo " Image: $IMAGE_TAG"
232+
echo " Health: $HEALTH_URL"
233+
echo " Log: $LOG_FILE"

trust_layer/app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3140,11 +3140,14 @@ async def get_stats():
31403140
now = time.monotonic()
31413141
if cache and now - cache["ts"] < 60:
31423142
return cache["data"]
3143-
proof_count = sum(1 for f in PROOFS_DIR.iterdir() if f.suffix == ".json") if PROOFS_DIR.exists() else 0
3143+
proof_files = sorted(PROOFS_DIR.glob("prf_*.json")) if PROOFS_DIR.exists() else []
3144+
proof_count = len(proof_files)
3145+
latest_proof_id = proof_files[-1].stem if proof_files else None
31443146
assess_count = sum(1 for f in ASSESSMENTS_DIR.iterdir() if f.suffix == ".json") if ASSESSMENTS_DIR.exists() else 0
31453147
data = {
31463148
"proofs_generated": proof_count,
31473149
"assessments_completed": assess_count,
3150+
"latest_proof_id": latest_proof_id,
31483151
}
31493152
get_stats._cache = {"ts": now, "data": data}
31503153
return data

0 commit comments

Comments
 (0)