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
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: 2
updates:
# Python dependencies. The library declares *ranged* requirements
# (httpx>=0.27, pydantic>=2) on purpose; Dependabot only proposes a change
# when a dependency drifts out of the declared range, so it never forces a
# pin on the library itself. Minor/patch bumps are grouped to limit noise.
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
python-minor-and-patch:
update-types:
- "minor"
- "patch"

# GitHub Actions. Keeps the SHA-pinned actions in ci.yml / publish.yml /
# codeql.yml fresh (it updates the pin and the version comment together).
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
github-actions:
patterns:
- "*"
185 changes: 150 additions & 35 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
name: CI

# Contract: this workflow runs the same gate as `make ci`
# (fmt-check -> lint -> type -> test -> example). Each step below invokes a
# Makefile target so the local gate and CI cannot drift (see Makefile and
# docs/agent-context/workflows.md). Change the steps here only by changing the
# Makefile.

on:
push:
branches: ["main", "copilot/**"]
pull_request:
branches: ["main"]
workflow_call:

# Least-privilege by default; jobs needing more declare it explicitly.
permissions:
contents: read

# Cancel superseded runs on the same ref so a new push to a PR stops the
# previous run instead of burning runner time. Keyed on the ref so distinct
# branches/PRs stay independent.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: "Python ${{ matrix.python-version }}"
Expand All @@ -18,64 +35,162 @@ jobs:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Lint (ruff check)
run: ruff check src/ tests/ examples/
# Each step is a Makefile target — see the contract note at the top.
- name: Format check
run: make fmt-check

- name: Format check (ruff format)
run: ruff format --check src/ tests/ examples/
- name: Lint
run: make lint

- name: Type check (mypy)
run: mypy src/
- name: Type check
run: make type

- name: Test (pytest)
run: python -m pytest -q --cov=weaver_kernel --cov-report=term-missing
- name: Test
run: make test

- name: Examples
run: |
python examples/basic_cli.py
python examples/billing_demo.py
python examples/http_driver_demo.py
python examples/tutorial.py
python examples/readme_quickstart.py
python examples/trace_export_demo.py
python examples/ocsf_export_demo.py
python examples/trace_replay_demo.py

conformance_stub:
name: "Weaver Spec Conformance Stub (v0.1.0)"
run: make example

- name: Coverage HTML report
if: always()
run: python -m coverage html

- name: Upload coverage HTML
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage-html-${{ matrix.python-version }}
path: htmlcov/
if-no-files-found: ignore

bare-install:
name: "Bare install (no extras)"
runs-on: ubuntu-latest
needs: test
permissions:
contents: read

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install dependencies
run: pip install -e ".[dev]"
# No extras: proves the "minimal deps (httpx + pydantic)" claim holds.
- name: Install (no extras)
run: pip install .

- name: Import the full public API
run: |
python -c "import weaver_kernel as w; \
missing = [n for n in w.__all__ if not hasattr(w, n)]; \
assert not missing, f'missing public symbols: {missing}'; \
print(f'imported {len(w.__all__)} public symbols')"

- name: Run the README quickstart
run: python examples/readme_quickstart.py

- name: Assert optional extras are genuinely absent
run: |
for mod in mcp yaml opentelemetry tiktoken weaver_contracts; do
if python -c "import $mod" 2>/dev/null; then
echo "::error::optional dependency '$mod' is importable in a bare install"
exit 1
fi
done
echo "no optional extras leaked into the base install"

# Placeholder: activate once dgenio/weaver-spec#4 ships the conformance runner.
# weaver-spec and weaver-contracts are published on PyPI.
# weaver_contracts.conformance does not yet exist (dgenio/weaver-spec#4).
# Replace this step with:
# pip install weaver-contracts # PyPI dist name uses a hyphen
# python -m weaver_contracts.conformance --target weaver_kernel
- name: weaver-spec conformance suite (stub)
- name: Assert the MCP-extra-missing error is helpful
run: |
echo "weaver-contracts 0.2.0 is on PyPI; weaver_contracts.conformance runner not yet available (dgenio/weaver-spec#4)."
echo "Stub passes. Activate when dgenio/weaver-spec#4 ships."
python - <<'PY'
from weaver_kernel.drivers.mcp_support import import_optional
try:
import_optional("mcp.client.session")
except ImportError as exc:
assert "weaver-kernel[mcp]" in str(exc), f"unhelpful error: {exc}"
print("MCP-extra-missing error is documented and actionable")
else:
raise AssertionError("expected ImportError without the mcp extra")
PY

security-audit:
name: "Dependency audit (pip-audit)"
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install pip-audit
run: pip install pip-audit

# Resolve weaver-kernel's *runtime* dependency tree in an isolated venv
# (no extras, no pip-audit) and audit exactly that, so pip-audit's own
# dependencies can never cause a failure unrelated to what adopters ship.
- name: Resolve runtime dependency tree
run: |
python -m venv /tmp/runtime
/tmp/runtime/bin/pip install .
/tmp/runtime/bin/pip freeze --exclude-editable \
| grep -viE '^(weaver-kernel|pip|setuptools)([=@ ]|$)' > runtime-requirements.txt
echo "Auditing:"; cat runtime-requirements.txt

# Policy: fail on any known vulnerability in the runtime tree. Document a
# false positive by appending `--ignore-vuln <ID>` here with a comment
# (see README "Security automation").
- name: Audit runtime dependencies
run: pip-audit --strict --desc --requirement runtime-requirements.txt

conformance:
name: "Weaver-spec conformance"
runs-on: ubuntu-latest
needs: test
permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

# Real validation (no echo): map kernel Frame/ActionTrace/token onto the
# published weaver-contracts dataclasses and assert they validate. When
# dgenio/weaver-spec#4 ships weaver_contracts.conformance, add its runner
# here as an additional step.
- name: Install conformance extra
run: pip install ".[conformance]" pytest pytest-asyncio

- name: Report contract version
run: python -c "from weaver_kernel.conformance import contract_version; print('weaver-contracts', contract_version())"

- name: Run conformance mapping tests
run: python -m pytest tests/test_conformance.py -q
40 changes: 40 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CodeQL

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
# Weekly, so newly-published advisories are caught even without a push.
- cron: "27 3 * * 1"

permissions:
contents: read

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

jobs:
analyze:
name: "Analyze (python)"
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required to upload CodeQL results to the Security tab

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Initialize CodeQL
uses: github/codeql-action/init@8272c299f21ca24af15dfe9ac0971ba969e5e0d5 # v3.36.2
with:
languages: python
queries: security-and-quality

# Python is interpreted — no build step is needed, so autobuild is omitted.
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8272c299f21ca24af15dfe9ac0971ba969e5e0d5 # v3.36.2
with:
category: "/language:python"
36 changes: 35 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,33 @@ jobs:
- name: Build sdist and wheel
run: python -m build

# Describe the *published package's* runtime tree (not the build env): a
# clean venv with only the built wheel installed, introspected by
# cyclonedx-py from a separate tool install so the tool's own deps do not
# pollute the SBOM. Written to sbom/ (NOT dist/) so it is never uploaded
# to PyPI as a distribution.
- name: Generate SBOM (CycloneDX)
run: |
python -m pip install cyclonedx-bom
python -m venv /tmp/wheelenv
/tmp/wheelenv/bin/pip install dist/*.whl
mkdir -p sbom
cyclonedx-py environment /tmp/wheelenv/bin/python \
--of JSON --mc-type library --pyproject pyproject.toml \
-o sbom/weaver-kernel.cdx.json

- name: Upload dist artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist
path: dist/

- name: Upload SBOM artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom
path: sbom/

release:
name: "GitHub Release"
needs: build
Expand All @@ -48,12 +69,20 @@ jobs:
name: dist
path: dist/

- name: Download SBOM artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: sbom
path: sbom/

- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
generate_release_notes: true
fail_on_unmatched_files: true
files: dist/*
files: |
dist/*
sbom/*

publish:
name: "Publish to PyPI"
Expand All @@ -72,3 +101,8 @@ jobs:

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
# PEP 740 attestations, signed via the Trusted Publisher OIDC identity
# (the id-token: write permission above). Verifiable on the PyPI
# project page and with the `pypi-attestations` CLI — see RELEASE.md.
attestations: true
Loading
Loading