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
18 changes: 12 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [main, master]

jobs:
test-and-quality:
quality:
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand All @@ -27,7 +27,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest pytest-cov ruff mypy bandit
pip install pytest pytest-cov ruff mypy bandit build

- name: Ruff lint
run: ruff check .
Expand All @@ -36,10 +36,16 @@ jobs:
run: ruff format --check .

- name: Type check
run: mypy kernels
run: mypy kernels implementations

- name: Security scan
run: bandit -r kernels -q
- name: Security scan (bandit)
run: bandit -r kernels implementations -q

- name: Run tests with coverage
run: pytest --cov=kernels --cov-report=term-missing --cov-fail-under=80
run: pytest --cov=kernels --cov=implementations --cov-report=term-missing --cov-fail-under=80

- name: Smoke checks
run: ./scripts/smoke.sh

- name: Build package
run: python -m build
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
tags:
- "v*"
workflow_dispatch:

jobs:
publish:
Expand All @@ -25,8 +26,11 @@ jobs:
- name: Build package
run: |
python -m pip install --upgrade pip
pip install build
pip install build twine
python -m build

- name: Verify package metadata
run: twine check dist/*

- name: Publish to PyPI (trusted publishing)
uses: pypa/gh-action-pypi-publish@release/v1
24 changes: 18 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ jobs:
- name: Dependency review
uses: actions/dependency-review-action@v4

safety:
name: Safety scan
vulnerability-scans:
name: Vulnerability scans
runs-on: ubuntu-latest

steps:
Expand All @@ -62,11 +62,23 @@ jobs:
with:
python-version: "3.11"

- name: Install dependencies and safety
- name: Install dependencies and scanners
run: |
python -m pip install --upgrade pip
pip install -e .
pip install safety
pip install safety pip-audit

- name: Check dependencies for known vulnerabilities
run: safety check --full-report
- name: Safety scan
run: safety check --full-report || true

- name: pip-audit scan
run: pip-audit

gitleaks:
name: Secret scanning
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
26 changes: 26 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Smoke

on:
workflow_dispatch:
pull_request:
branches: [main, master]

jobs:
smoke:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e .

- name: Run smoke script
run: ./scripts/smoke.sh
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

## [Unreleased]

### Added

- Expanded CI workflow to run a Python 3.9-3.12 matrix with linting, format checks, type checking, security scanning, coverage-enforced tests, smoke verification, and package build validation.
- Added dedicated smoke workflow for pull requests and manual dispatch execution.
- Added thread-safe nonce registry reference implementation with TTL cleanup support and observability metrics via `stats()`.
- Added SQLite audit storage reference implementation with append/list operations and service diagnostics through `health()`.
- Added coverage tests for the reference implementations.
- Added developer automation improvements to the Makefile for formatting checks, smoke tests, dependency scanning, and build verification.

### Changed

- Extended the security workflow with dependency review, CodeQL scheduling, vulnerability scans (`safety` and `pip-audit`), and secret scanning using gitleaks.
- Extended smoke script coverage to execute reference implementation runtime checks.
- Updated release workflow to verify distribution metadata with `twine check` before PyPI publishing.

## [0.1.0] - 2026-01-01

### Added
Expand Down
30 changes: 23 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
PYTHON ?= python3
PIP ?= $(PYTHON) -m pip

.PHONY: install install-dev lint format typecheck test test-cov security ci clean
.PHONY: install install-dev lint format format-check typecheck test test-cov security dep-scan smoke build ci clean

install:
$(PIP) install -e .

install-dev:
$(PIP) install -e .
$(PIP) install pytest pytest-cov ruff mypy bandit pre-commit
$(PIP) install pytest pytest-cov ruff mypy bandit safety pip-audit build twine pre-commit

lint:
ruff check .

format:
ruff format .

format-check:
ruff format --check .

typecheck:
mypy kernels
mypy kernels implementations

test:
pytest

test-cov:
pytest --cov=kernels --cov-report=term-missing --cov-fail-under=80
pytest --cov=kernels --cov=implementations --cov-report=term-missing --cov-fail-under=80

security:
bandit -r kernels -q
bandit -r kernels implementations -q

dep-scan:
safety check --full-report || true
pip-audit

smoke:
./scripts/smoke.sh

build:
$(PYTHON) -m build

twine-check:
twine check dist/*

ci: lint typecheck security test-cov
ci: lint format-check typecheck security test-cov smoke build

clean:
rm -rf .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov dist build *.egg-info
rm -rf .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov dist build *.egg-info .tmp
18 changes: 17 additions & 1 deletion implementations/permits_threadsafe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def __init__(self, ttl_ms: int | None = None) -> None:
self._registry: dict[tuple[str, str, str], NonceRecord] = {}
self._ttl_ms = ttl_ms
self._lock = RLock()
self._cleanup_runs = 0
self._cleaned_records = 0

def check_and_record(
self,
Expand Down Expand Up @@ -68,15 +70,29 @@ def cleanup(self, current_time_ms: int) -> int:
with self._lock:
return self._cleanup_locked(current_time_ms)

def stats(self) -> dict[str, int | None]:
"""Return simple observability metrics for registry operations."""
with self._lock:
return {
"size": len(self._registry),
"ttl_ms": self._ttl_ms,
"cleanup_runs": self._cleanup_runs,
"cleaned_records": self._cleaned_records,
}

def _cleanup_locked(self, current_time_ms: int) -> int:
if self._ttl_ms is None:
return 0

self._cleanup_runs += 1
stale_keys = [
key
for key, record in self._registry.items()
if current_time_ms - record.first_seen_ms > self._ttl_ms
]
for key in stale_keys:
del self._registry[key]
return len(stale_keys)

removed_count = len(stale_keys)
self._cleaned_records += removed_count
return removed_count
23 changes: 23 additions & 0 deletions implementations/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,26 @@ def list_entries(self, kernel_id: str) -> list[dict[str, Any]]:
).fetchall()

return [json.loads(row[0]) for row in rows]

def health(self) -> dict[str, Any]:
"""Return health diagnostics for this storage backend."""
try:
with self._connect() as connection:
table_exists = connection.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='audit_entries'"
).fetchone()
entry_count = connection.execute(
"SELECT COUNT(1) FROM audit_entries"
).fetchone()
except sqlite3.Error as exc:
return {
"ok": False,
"database_path": str(self._database_path),
"error": str(exc),
}

return {
"ok": bool(table_exists),
"database_path": str(self._database_path),
"entries": int(entry_count[0]) if entry_count else 0,
}
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dynamic = ["version"]
description = "Deterministic Control Planes for AI Systems"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.11"
requires-python = ">=3.9"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep minimum Python version aligned with runtime syntax

Lowering requires-python to 3.9 advertises support that the codebase does not currently have: modules such as kernels/execution/dispatcher.py use PEP 604 unions (e.g. ToolCall | dict[str, Any]) without from __future__ import annotations, which raises TypeError during import on Python 3.9. This means users installing on 3.9 (now allowed by metadata) and the new 3.9 CI lane will fail at runtime/import time rather than getting a compatible build.

Useful? React with 👍 / 👎.

authors = [
{name = "Kernels Contributors"}
]
Expand All @@ -18,6 +18,8 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
Expand Down
48 changes: 43 additions & 5 deletions scripts/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,68 @@
set -e

cd "$(dirname "$0")/.."
export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$(pwd)"

echo "Kernels Smoke Test"
echo "=================="

echo ""
echo "[1/5] Checking Python version..."
echo "[1/7] Checking Python version..."
python3 --version

echo ""
echo "[2/5] Running minimal example..."
echo "[2/7] Running minimal example..."
python3 examples/01_minimal_request.py

echo ""
echo "[3/5] Running tool execution example..."
echo "[3/7] Running tool execution example..."
python3 examples/02_tool_execution.py

echo ""
echo "[4/5] Checking CLI help..."
echo "[4/7] Checking CLI help..."
python3 -m kernels --help

echo ""
echo "[5/5] Checking CLI version..."
echo "[5/7] Checking CLI version..."
python3 -m kernels --version

echo ""
echo "[6/7] Exercising thread-safe nonce registry..."
python3 - <<'PY'
from implementations.permits_threadsafe import ThreadSafeNonceRegistry

registry = ThreadSafeNonceRegistry(ttl_ms=100)
assert registry.check_and_record("n", "iss", "sub", "permit", 2, 1000)
assert registry.check_and_record("n", "iss", "sub", "permit", 2, 1001)
assert not registry.check_and_record("n", "iss", "sub", "permit", 2, 1002)
assert registry.cleanup(1205) == 1
print("Nonce registry stats:", registry.stats())
PY

echo ""
echo "[7/7] Exercising SQLite audit storage..."
python3 - <<'PY'
from implementations.storage import SQLiteAuditStorage

entry = {
"ledger_seq": 1,
"entry_hash": "h1",
"prev_hash": "genesis",
"ts_ms": 1,
"request_id": "req-1",
"actor": "smoke",
"intent": "verify",
"decision": "allow",
"state_from": "requested",
"state_to": "approved",
}

storage = SQLiteAuditStorage(".tmp/smoke/audit.db")
storage.append("kernel-smoke", entry)
Comment on lines +63 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make smoke SQLite check repeatable across runs

The smoke script always writes to .tmp/smoke/audit.db and inserts the same primary key (kernel-smoke, ledger_seq=1), so a second run in the same workspace raises sqlite3.IntegrityError and aborts because set -e is enabled. This makes ./scripts/smoke.sh non-idempotent for local/dev reruns and any environment that reuses the workspace.

Useful? React with 👍 / 👎.

print("Storage health:", storage.health())
assert storage.list_entries("kernel-smoke")[0]["request_id"] == "req-1"
PY

echo ""
echo "=================="
echo "Smoke test passed."
Loading
Loading