Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/ISSUE_TEMPLATE/release-readiness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Release Readiness
description: Track release-readiness gates before deploying to production.
title: "Release readiness: <version>"
labels: ["release"]
body:
- type: markdown
attributes:
value: |
Complete every section before approving production deployment.
Items marked ⛔ are **blocking** — the release cannot proceed until they are resolved.

- type: checkboxes
id: ci
attributes:
label: "CI & Quality"
options:
- label: "⛔ All CI checks pass on the release commit (build, tests, lint, type-check)"
required: true
- label: "⛔ Test coverage meets the configured bar; no unticketted skip/xfail markers"
required: true
- label: "⛔ No plaintext secrets or credentials in the diff"
required: true

- type: checkboxes
id: correctness
attributes:
label: "Code Correctness"
options:
- label: "⛔ No known correctness bugs"
required: true
- label: "⛔ All new behaviour covered by tests"
required: true
- label: "⛔ Irreversible changes have a documented rollback path"
required: true

- type: checkboxes
id: security
attributes:
label: "Security"
options:
- label: "⛔ No new security gaps introduced"
required: true
- label: "Sensitive operations are audit-logged"

- type: checkboxes
id: migrations
attributes:
label: "Database Migrations"
options:
- label: "Migrations follow expand → migrate → contract pattern"
- label: "Migrations are backward-compatible"
- label: "Rollback migration written and tested"

- type: checkboxes
id: feature_flags
attributes:
label: "Feature Flags & Progressive Rollout"
options:
- label: "Risky changes are behind a feature flag (default off)"
- label: "Kill switch available and tested"
- label: "Rollout plan defined: canary → % rollout → 100%"

- type: checkboxes
id: ops
attributes:
label: "Operational Readiness"
options:
- label: "Dashboards updated / new metrics added"
- label: "Alerts configured for new failure modes"
- label: "Runbook updated or created"
- label: "On-call team briefed"

- type: checkboxes
id: docs
attributes:
label: "Documentation & Acceptance"
options:
- label: "API docs / OpenAPI spec updated"
- label: "Release notes / CHANGELOG entry added"
- label: "Acceptance criteria verified"
- label: "Stakeholder / PM sign-off recorded"

- type: input
id: signoff
attributes:
label: Stakeholder sign-off
description: Link to approval or @mention the approver.
placeholder: "e.g. Approved by @pm in #releases — 2026-01-15"
validations:
required: true

- type: textarea
id: rollback
attributes:
label: Rollback procedure
description: Step-by-step instructions to revert this release if it fails.
placeholder: |
1. Run `./scripts/rollback.sh <previous-version>`
2. Verify health checks pass
3. Notify on-call channel
validations:
required: true
73 changes: 73 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
## What & Why

<!-- One paragraph: what changed and why. Link the related issue/story. -->

Closes #

## How to verify

<!-- Step-by-step instructions a reviewer can follow to confirm the change works. -->

1.
2.

## Risk & rollback

<!-- What could go wrong? How is this reverted if it fails in production? -->

- **Risk:**
- **Rollback:**

---

## Release-Readiness Checklist

> Complete **every** item before requesting review. Items marked ⛔ are
> merge-blocking — the PR cannot land until they are resolved.

### CI & Quality

- [ ] ⛔ All CI checks pass (build, tests, lint, type-check)
- [ ] ⛔ Test coverage meets the bar; no new `skip`/`xfail` markers without a linked ticket
- [ ] ⛔ PR diff is under 400 lines — split if larger
- [ ] ⛔ No plaintext secrets or credentials in the diff

### Code Correctness

- [ ] ⛔ No known correctness bugs introduced
- [ ] ⛔ New behaviour is covered by at least one test
- [ ] ⛔ Irreversible changes (schema drops, data migrations) have a documented rollback path

### Security

- [ ] ⛔ No new security gaps (injection, auth bypass, exposed endpoints)
- [ ] Input validated and sanitised where applicable
- [ ] Sensitive operations are audit-logged

### Database Migrations

- [ ] Migration follows expand → migrate → contract pattern
- [ ] Migration is backward-compatible with the previous deploy artifact
- [ ] Rollback migration written and tested locally

### Feature Flags & Progressive Rollout

- [ ] Risky changes are behind a feature flag (default **off**)
- [ ] Kill switch is available and tested
- [ ] Rollout plan: canary → % rollout → 100%

### Operational Readiness

- [ ] Dashboards updated / new metrics added
- [ ] Alerts configured for new failure modes
- [ ] Runbook updated or created
- [ ] On-call team briefed if behaviour changes

### Documentation & Acceptance

- [ ] API docs / OpenAPI spec updated (if endpoints changed)
- [ ] Release notes / CHANGELOG entry added
- [ ] Acceptance criteria from the story verified
- [ ] Stakeholder / PM sign-off recorded (paste link or @mention below)

**Sign-off:** <!-- @mention or link to approval -->
127 changes: 127 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# ── 1. PR size gate ──────────────────────────────────────────────────────────
pr-size:
name: PR size gate (< 400 lines)
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Fetch diff size
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ADDITIONS=$(gh pr view ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--json additions,deletions \
--jq '.additions + .deletions')
echo "Diff size: ${ADDITIONS} lines"
if [ "$ADDITIONS" -gt 400 ]; then
echo "::error::PR diff is ${ADDITIONS} lines — exceeds the 400-line review limit."
echo "::error::Split this PR into smaller, focused changes. See code-review-standards.md."
exit 1
fi

# ── 2. Backend: lint, type-check, tests, coverage ────────────────────────────
backend:
name: Backend — tests & coverage
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: backend/requirements.txt

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
pip install pytest-cov ruff mypy

- name: Lint (ruff)
run: ruff check .

- name: Type-check (mypy)
run: mypy app/ --ignore-missing-imports --strict-optional

- name: Tests + coverage
run: |
pytest tests/ \
--cov=app \
--cov-report=term-missing \
--cov-report=xml:coverage.xml \
--cov-fail-under=60 \
-v
env:
DATABASE_URL: "sqlite+aiosqlite:///:memory:"

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-coverage
path: backend/coverage.xml

# ── 3. Frontend: lint, type-check, build ─────────────────────────────────────
frontend:
name: Frontend — lint, type-check & build
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint (eslint)
run: npm run lint

- name: Type-check (tsc)
run: npx tsc --noEmit

- name: Build
run: npm run build

# ── 4. Gate summary — all required checks must pass ──────────────────────────
ci-gate:
name: CI gate (required)
if: always()
needs: [backend, frontend]
runs-on: ubuntu-latest
steps:
- name: Check required jobs
run: |
BACKEND="${{ needs.backend.result }}"
FRONTEND="${{ needs.frontend.result }}"
echo "backend=${BACKEND} frontend=${FRONTEND}"
if [ "$BACKEND" != "success" ] || [ "$FRONTEND" != "success" ]; then
echo "::error::One or more required CI jobs did not succeed."
exit 1
fi
echo "All required checks passed."
2 changes: 1 addition & 1 deletion backend/agent/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _resolve_dns_impl(target_url: str) -> str:
for family, _, _, _, sockaddr in socket.getaddrinfo(host, None):
ip = sockaddr[0]
fam = "IPv6" if family == socket.AF_INET6 else "IPv4"
addrs.append({"family": fam, "address": ip})
addrs.append({"family": fam, "address": ip}) # type: ignore[dict-item] # mixed str/int values
except OSError as e:
addrs = [{"error": str(e)}]

Expand Down
2 changes: 1 addition & 1 deletion backend/agent/vecna_litellm.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async def _vecna_acompletion(*args: Any, **kwargs: Any):

litellm_main.completion = _vecna_completion
litellm_main.acompletion = _vecna_acompletion
import litellm as _litellm_pkg
import litellm as _litellm_pkg # noqa: E402

_litellm_pkg.completion = _vecna_completion
_litellm_pkg.acompletion = _vecna_acompletion
Expand Down
5 changes: 3 additions & 2 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import time
import uuid
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect
from fastapi import Depends, FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.responses import PlainTextResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import async_sessionmaker

from app.config import Settings # loads agent.vecna_litellm (patches litellm for OpenRouter)

Expand All @@ -24,6 +23,7 @@
from app.services.pricing import display_billable_total, tool_pricing_public_snapshot
from app.services.rate_limit import current_limits, ops_rate_limit
from app.services.runner import cancel_task, execute_operation, register_task
from app.routers import dora as dora_router
from app.schema_migrate import _sqlite_add_missing_columns
from app.url_guard import is_safe_public_target

Expand Down Expand Up @@ -67,6 +67,7 @@ async def lifespan(app: FastAPI):


app = FastAPI(title="Vecna Operations Console", lifespan=lifespan)
app.include_router(dora_router.router)
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in settings.cors_origins.split(",") if o.strip()],
Expand Down
1 change: 1 addition & 0 deletions backend/app/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# routers package
Loading
Loading