Skip to content

Commit 9ccac84

Browse files
authored
Merge pull request #4 from Shards-foundation/codex/enhance-ci-with-multi-version-testing-and-security
ci: harden CI/security pipelines, add smoke workflow and reference implementations
2 parents 830af70 + d8b474f commit 9ccac84

File tree

11 files changed

+275
-27
lines changed

11 files changed

+275
-27
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: [main, master]
88

99
jobs:
10-
test-and-quality:
10+
quality:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
fail-fast: false
@@ -27,7 +27,7 @@ jobs:
2727
run: |
2828
python -m pip install --upgrade pip
2929
pip install -e .
30-
pip install pytest pytest-cov ruff mypy bandit
30+
pip install pytest pytest-cov ruff mypy bandit build
3131
3232
- name: Ruff lint
3333
run: ruff check .
@@ -36,10 +36,16 @@ jobs:
3636
run: ruff format --check .
3737

3838
- name: Type check
39-
run: mypy kernels
39+
run: mypy kernels implementations
4040

41-
- name: Security scan
42-
run: bandit -r kernels -q
41+
- name: Security scan (bandit)
42+
run: bandit -r kernels implementations -q
4343

4444
- name: Run tests with coverage
45-
run: pytest --cov=kernels --cov-report=term-missing --cov-fail-under=80
45+
run: pytest --cov=kernels --cov=implementations --cov-report=term-missing --cov-fail-under=80
46+
47+
- name: Smoke checks
48+
run: ./scripts/smoke.sh
49+
50+
- name: Build package
51+
run: python -m build

.github/workflows/release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
tags:
66
- "v*"
7+
workflow_dispatch:
78

89
jobs:
910
publish:
@@ -25,8 +26,11 @@ jobs:
2526
- name: Build package
2627
run: |
2728
python -m pip install --upgrade pip
28-
pip install build
29+
pip install build twine
2930
python -m build
3031
32+
- name: Verify package metadata
33+
run: twine check dist/*
34+
3135
- name: Publish to PyPI (trusted publishing)
3236
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/security.yml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ jobs:
4949
- name: Dependency review
5050
uses: actions/dependency-review-action@v4
5151

52-
safety:
53-
name: Safety scan
52+
vulnerability-scans:
53+
name: Vulnerability scans
5454
runs-on: ubuntu-latest
5555

5656
steps:
@@ -62,11 +62,23 @@ jobs:
6262
with:
6363
python-version: "3.11"
6464

65-
- name: Install dependencies and safety
65+
- name: Install dependencies and scanners
6666
run: |
6767
python -m pip install --upgrade pip
6868
pip install -e .
69-
pip install safety
69+
pip install safety pip-audit
7070
71-
- name: Check dependencies for known vulnerabilities
72-
run: safety check --full-report
71+
- name: Safety scan
72+
run: safety check --full-report || true
73+
74+
- name: pip-audit scan
75+
run: pip-audit
76+
77+
gitleaks:
78+
name: Secret scanning
79+
runs-on: ubuntu-latest
80+
steps:
81+
- name: Checkout
82+
uses: actions/checkout@v4
83+
- name: Run gitleaks
84+
uses: gitleaks/gitleaks-action@v2

.github/workflows/smoke.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Smoke
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches: [main, master]
7+
8+
jobs:
9+
smoke:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.11"
19+
20+
- name: Install package
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install -e .
24+
25+
- name: Run smoke script
26+
run: ./scripts/smoke.sh

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
44

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

7+
## [Unreleased]
8+
9+
### Added
10+
11+
- 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.
12+
- Added dedicated smoke workflow for pull requests and manual dispatch execution.
13+
- Added thread-safe nonce registry reference implementation with TTL cleanup support and observability metrics via `stats()`.
14+
- Added SQLite audit storage reference implementation with append/list operations and service diagnostics through `health()`.
15+
- Added coverage tests for the reference implementations.
16+
- Added developer automation improvements to the Makefile for formatting checks, smoke tests, dependency scanning, and build verification.
17+
18+
### Changed
19+
20+
- Extended the security workflow with dependency review, CodeQL scheduling, vulnerability scans (`safety` and `pip-audit`), and secret scanning using gitleaks.
21+
- Extended smoke script coverage to execute reference implementation runtime checks.
22+
- Updated release workflow to verify distribution metadata with `twine check` before PyPI publishing.
23+
724
## [0.1.0] - 2026-01-01
825

926
### Added

Makefile

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,50 @@
11
PYTHON ?= python3
22
PIP ?= $(PYTHON) -m pip
33

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

66
install:
77
$(PIP) install -e .
88

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

1313
lint:
1414
ruff check .
1515

1616
format:
1717
ruff format .
1818

19+
format-check:
20+
ruff format --check .
21+
1922
typecheck:
20-
mypy kernels
23+
mypy kernels implementations
2124

2225
test:
2326
pytest
2427

2528
test-cov:
26-
pytest --cov=kernels --cov-report=term-missing --cov-fail-under=80
29+
pytest --cov=kernels --cov=implementations --cov-report=term-missing --cov-fail-under=80
2730

2831
security:
29-
bandit -r kernels -q
32+
bandit -r kernels implementations -q
33+
34+
dep-scan:
35+
safety check --full-report || true
36+
pip-audit
37+
38+
smoke:
39+
./scripts/smoke.sh
40+
41+
build:
42+
$(PYTHON) -m build
43+
44+
twine-check:
45+
twine check dist/*
3046

31-
ci: lint typecheck security test-cov
47+
ci: lint format-check typecheck security test-cov smoke build
3248

3349
clean:
34-
rm -rf .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov dist build *.egg-info
50+
rm -rf .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov dist build *.egg-info .tmp

implementations/permits_threadsafe.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ def __init__(self, ttl_ms: int | None = None) -> None:
1515
self._registry: dict[tuple[str, str, str], NonceRecord] = {}
1616
self._ttl_ms = ttl_ms
1717
self._lock = RLock()
18+
self._cleanup_runs = 0
19+
self._cleaned_records = 0
1820

1921
def check_and_record(
2022
self,
@@ -68,15 +70,29 @@ def cleanup(self, current_time_ms: int) -> int:
6870
with self._lock:
6971
return self._cleanup_locked(current_time_ms)
7072

73+
def stats(self) -> dict[str, int | None]:
74+
"""Return simple observability metrics for registry operations."""
75+
with self._lock:
76+
return {
77+
"size": len(self._registry),
78+
"ttl_ms": self._ttl_ms,
79+
"cleanup_runs": self._cleanup_runs,
80+
"cleaned_records": self._cleaned_records,
81+
}
82+
7183
def _cleanup_locked(self, current_time_ms: int) -> int:
7284
if self._ttl_ms is None:
7385
return 0
7486

87+
self._cleanup_runs += 1
7588
stale_keys = [
7689
key
7790
for key, record in self._registry.items()
7891
if current_time_ms - record.first_seen_ms > self._ttl_ms
7992
]
8093
for key in stale_keys:
8194
del self._registry[key]
82-
return len(stale_keys)
95+
96+
removed_count = len(stale_keys)
97+
self._cleaned_records += removed_count
98+
return removed_count

implementations/storage.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,26 @@ def list_entries(self, kernel_id: str) -> list[dict[str, Any]]:
8282
).fetchall()
8383

8484
return [json.loads(row[0]) for row in rows]
85+
86+
def health(self) -> dict[str, Any]:
87+
"""Return health diagnostics for this storage backend."""
88+
try:
89+
with self._connect() as connection:
90+
table_exists = connection.execute(
91+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='audit_entries'"
92+
).fetchone()
93+
entry_count = connection.execute(
94+
"SELECT COUNT(1) FROM audit_entries"
95+
).fetchone()
96+
except sqlite3.Error as exc:
97+
return {
98+
"ok": False,
99+
"database_path": str(self._database_path),
100+
"error": str(exc),
101+
}
102+
103+
return {
104+
"ok": bool(table_exists),
105+
"database_path": str(self._database_path),
106+
"entries": int(entry_count[0]) if entry_count else 0,
107+
}

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ dynamic = ["version"]
88
description = "Deterministic Control Planes for AI Systems"
99
readme = "README.md"
1010
license = {text = "MIT"}
11-
requires-python = ">=3.11"
11+
requires-python = ">=3.9"
1212
authors = [
1313
{name = "Kernels Contributors"}
1414
]
@@ -18,6 +18,8 @@ classifiers = [
1818
"License :: OSI Approved :: MIT License",
1919
"Operating System :: OS Independent",
2020
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.9",
22+
"Programming Language :: Python :: 3.10",
2123
"Programming Language :: Python :: 3.11",
2224
"Programming Language :: Python :: 3.12",
2325
"Topic :: Software Development :: Libraries :: Python Modules",

scripts/smoke.sh

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,68 @@
44
set -e
55

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

89
echo "Kernels Smoke Test"
910
echo "=================="
1011

1112
echo ""
12-
echo "[1/5] Checking Python version..."
13+
echo "[1/7] Checking Python version..."
1314
python3 --version
1415

1516
echo ""
16-
echo "[2/5] Running minimal example..."
17+
echo "[2/7] Running minimal example..."
1718
python3 examples/01_minimal_request.py
1819

1920
echo ""
20-
echo "[3/5] Running tool execution example..."
21+
echo "[3/7] Running tool execution example..."
2122
python3 examples/02_tool_execution.py
2223

2324
echo ""
24-
echo "[4/5] Checking CLI help..."
25+
echo "[4/7] Checking CLI help..."
2526
python3 -m kernels --help
2627

2728
echo ""
28-
echo "[5/5] Checking CLI version..."
29+
echo "[5/7] Checking CLI version..."
2930
python3 -m kernels --version
3031

32+
echo ""
33+
echo "[6/7] Exercising thread-safe nonce registry..."
34+
python3 - <<'PY'
35+
from implementations.permits_threadsafe import ThreadSafeNonceRegistry
36+
37+
registry = ThreadSafeNonceRegistry(ttl_ms=100)
38+
assert registry.check_and_record("n", "iss", "sub", "permit", 2, 1000)
39+
assert registry.check_and_record("n", "iss", "sub", "permit", 2, 1001)
40+
assert not registry.check_and_record("n", "iss", "sub", "permit", 2, 1002)
41+
assert registry.cleanup(1205) == 1
42+
print("Nonce registry stats:", registry.stats())
43+
PY
44+
45+
echo ""
46+
echo "[7/7] Exercising SQLite audit storage..."
47+
python3 - <<'PY'
48+
from implementations.storage import SQLiteAuditStorage
49+
50+
entry = {
51+
"ledger_seq": 1,
52+
"entry_hash": "h1",
53+
"prev_hash": "genesis",
54+
"ts_ms": 1,
55+
"request_id": "req-1",
56+
"actor": "smoke",
57+
"intent": "verify",
58+
"decision": "allow",
59+
"state_from": "requested",
60+
"state_to": "approved",
61+
}
62+
63+
storage = SQLiteAuditStorage(".tmp/smoke/audit.db")
64+
storage.append("kernel-smoke", entry)
65+
print("Storage health:", storage.health())
66+
assert storage.list_entries("kernel-smoke")[0]["request_id"] == "req-1"
67+
PY
68+
3169
echo ""
3270
echo "=================="
3371
echo "Smoke test passed."

0 commit comments

Comments
 (0)