Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Version control
.git/
.gitignore
.github/

# Python build/test artifacts
__pycache__/
*.py[cod]
*.egg-info/
build/
dist/
.mypy_cache/
.ruff_cache/
.pytest_cache/
.hypothesis/
.coverage
htmlcov/

# Frontend (served separately; not needed inside the server image)
web/node_modules/
web/dist/
web/coverage/

# Docs, fixtures and demo data are not required by the server image
docs/
fixtures/
data/
examples/
schemas/
scripts/
tests/

# Local artifacts
*.apiverity/
.apiverity-bundles/
demo-data/
.env*
Dockerfile
.dockerignore
11 changes: 8 additions & 3 deletions .github/workflows/api-verity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ jobs:
id: specs
run: |
git fetch origin "${{ github.base_ref }}" --depth=1
# Allowlist, not denylist: only files under known contract directories are
# treated as API specs. A denylist leaks -- every new root-level YAML
# (.pre-commit-config.yaml, and any future tool config) would otherwise be
# handed to `apiverity validate` and fail the gate. SPEC_DIRS is the one
# place to extend when contracts move or a new location is added.
SPEC_DIRS='^(fixtures/apis|openapi|specs|contracts)/'
SPECS=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" \
| grep -E '\.(yaml|yml|json)$' \
| grep -Eiv 'package|lock|\.github' \
| grep -Ev '^(data|web|schemas|docs|build|dist)/' || true)
| grep -E '[.](yaml|yml|json)$' \
| grep -E "$SPEC_DIRS" || true)
echo "specs<<EOF" >> "$GITHUB_OUTPUT"
echo "$SPECS" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Type check
run: mypy apiverity
- name: Tests with coverage
run: pytest tests/ -q --cov=apiverity --cov-report=term-missing --cov-fail-under=60
run: pytest tests/ -q --cov=apiverity --cov-report=term-missing --cov-fail-under=72
- name: Build distribution
run: python -m build
- name: Dependency scan (pip-audit)
Expand Down
104 changes: 104 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: read

jobs:
build:
name: Build sdist & wheel
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v5.6.0
with:
python-version: "3.12"
- name: Install build tool
run: python -m pip install --upgrade pip && pip install build
- name: Build distributions
run: python -m build
- name: Check metadata consistency
run: |
pip install twine
twine check dist/*
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: distributions
path: dist/
if-no-files-found: error

pypi-publish:
name: Publish to PyPI (trusted publishing)
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/api-verity-lab
permissions:
id-token: write # OIDC trusted publishing — no API token stored
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: distributions
path: dist/
- name: Publish
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4

github-release:
name: GitHub release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: distributions
path: dist/
- name: Create release with artifacts
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$TAG" dist/* \
--title "api-verity-lab $TAG" \
--generate-notes

docker-image:
name: Build & push server image to GHCR
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log in to GHCR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract image metadata
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/${{ github.repository }}-server
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repos:
- id: detect-private-key

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.5
rev: v0.16.4
hooks:
- id: ruff
args: [--fix]
Expand Down
8 changes: 7 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ apiverity/
├── reports/ terminal/json/yaml/markdown/junit/sarif/html reporters
├── exporters/ .apiverity bundle writer with checksums
├── plugins/ Plugin loader + versioned plugin API protocols
└── cli/ Click-based CLI: 18 commands, JSON output, exit codes
├── security/ Defensive security checks + rule packs
├── server/ Self-hosted Flask monolith: api.py (route factory),
│ store.py (SQLite persistence), schema.py (DDL + helpers),
│ decision.py (can-i-deploy), auth/jobs/webhooks
└── cli/ argparse-based CLI: parser in main.py, implementations in
commands/ grouped by lane (governance, testing, runtime,
artifacts, platform) with shared plumbing in commands/common
```

## Data flow
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes. Format based on Keep a Changelog; versions are semver.
## [Unreleased]

### Fixed
- **Console script entry point** pointed at a nonexistent symbol
(`apiverity.cli.main:cli`); installing the package produced an `apiverity`
command that crashed with ImportError. Now `apiverity.cli.main:main`.
- GraphQL spec plugin silently loaded **zero operations** from valid SDL:
graphql-core node kinds are snake_case (`object_type_definition`) while the
loader compared camelCase strings. Root-type fields now normalize correctly
Expand All @@ -16,9 +19,38 @@ All notable changes. Format based on Keep a Changelog; versions are semver.
- CI dependency audit no longer hides failures behind `|| true`.
- Lint/format drift under ruff 0.16 normalized; pytest-asyncio loop-scope
configured explicitly.
- ARCHITECTURE.md incorrectly described the CLI as Click-based; it is
argparse-based (doc drift).

### Changed
- Frontend restructured from a single-file app into `components/`, `hooks/`
and domain-grouped `pages/` modules (`overview`, `contract`, `testing`,
`runtime`, `team`) with a central page registry — same behavior, now
maintainable and code-split-ready.
- CLI split into `apiverity/cli/commands/` grouped by product lane
(`common`, `governance`, `testing`, `runtime`, `artifacts`, `platform`);
`apiverity.cli.main` remains the stable entry point and re-exports all
command functions.
- Server store schema extracted into `apiverity/server/schema.py` (DDL,
timestamp/token helpers) and can-i-deploy / auth-fallback logic into
`apiverity/server/decision.py`; `Store` and `create_app` keep their public
signatures and `apiverity.server.api` re-exports the moved helpers.
- Tests organized into `tests/unit/` (pure logic) and `tests/integration/`
(mock server + self-hosted API over live HTTP); CI coverage floor raised
from 60% to 72% (current measured coverage: 76%).
- pre-commit ruff hook bumped to v0.16.4 to match the ruff version used for
formatting in CI; removed dead `_start_mock` helper and stray one-off
maintenance script.

### Added
- Release engineering: tag-triggered GitHub Actions release workflow with
PyPI trusted publishing (OIDC, no stored tokens), signed-off GitHub
Releases with distribution artifacts, and a GHCR container image for the
self-hosted server; repo ships a hardened non-root `Dockerfile`
(healthcheck on `/healthz`, volume-backed SQLite storage) plus
`.dockerignore`.
- Protocol-aware compatibility analysis: GraphQL breaking rules plus a

distinct *dangerous-change* category (field additions, return-type
relaxation); gRPC/protobuf wire-compatibility rules (RPC removal, message
type swaps, scalar wire-type changes, integer-width changes, enum-value
Expand Down
37 changes: 37 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Self-hosted API Verity Lab server.
#
# Build: docker build -t apiverity-server .
# Run: docker run -p 8090:8090 -v verity-data:/data apiverity-server
#
# The server stores everything in a single SQLite file; mount a volume at
# /data for persistence. Configuration via environment variables:
# VERITY_DB - SQLite database path inside the container (default /data/verity.db)
# VERITY_PORT - listen port (default 8090)

FROM python:3.12-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

COPY pyproject.toml README.md LICENSE NOTICE ./
COPY apiverity ./apiverity

RUN pip install .

RUN useradd --system --create-home --uid 10001 verity \
&& mkdir -p /data && chown verity:verity /data
USER verity

ENV VERITY_DB=/data/verity.db \
VERITY_PORT=8090

EXPOSE 8090

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD python -c "import os,urllib.request; urllib.request.urlopen('http://127.0.0.1:' + os.environ.get('VERITY_PORT','8090') + '/healthz', timeout=2)"

CMD ["python", "-c", "import os; from apiverity.server import Store, create_app; app = create_app(Store(os.environ.get('VERITY_DB', '/data/verity.db'))); app.run(host='0.0.0.0', port=int(os.environ.get('VERITY_PORT', '8090')))"]
1 change: 1 addition & 0 deletions apiverity/cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Versioned CLI command implementations, grouped by product lane."""
Loading
Loading