Files
chemenu/.gitea/workflows/ci.yml
T
torben 82a22eaa93
CI / verify (push) Successful in 53s
Release / release (push) Successful in 36s
stack: Telemetrie-Default nach Installationsform, Byte-Deckel und Session-Retention (schliesst #55)
Files changed:
- .gitea/workflows/ci.yml
- .gitignore
- CHANGES.md
- EVALS.md
- INSTALL-MCP.md
- INSTALL.md
- VERSION
- instructions/setup-instance.md
- reports/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/commands/doctor.py
- tools/chemenu/config.py
- tools/chemenu/mcp/server.py
- tools/chemenu/telemetry/policy.py
- tools/chemenu/telemetry/schema.py
- tools/chemenu/telemetry/writer.py
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_telemetry_emit.py
- tools/chemenu/tests/test_telemetry_policy.py
- tools/chemenu/version.py
2026-09-10 23:38:22 +02:00

261 lines
12 KiB
YAML

# CI for the wiki stack.
#
# One job, stopping at the first failure - the stack has no artifact to build
# and nothing to deploy, so the pipeline's whole job is "does the machinery
# still hold together, and does the distribution it produces still work".
#
# Runner: `linux-docker` is one of this Gitea instance's three routing labels
# (alongside `container-builder` and `k3s-deploy`). The job image is named
# explicitly rather than inherited from the runner's label mapping, which is
# not documented anywhere: `debian:trixie-slim` is the base the instance's
# container-build workflows already use, and Trixie's python3 is 3.13, past
# the 3.11 floor `doctor` enforces.
#
# Pinning that image makes `nodejs` this workflow's own responsibility.
# `actions/checkout` is a JavaScript action, and act_runner runs it with `node`
# *inside the job container* - a slim Debian has none, and the job dies with
# exit 127 before any step of ours runs. The shape below (apt `nodejs` first,
# then `checkout@v7`) is the one proven on this instance by
# torben/gitea-mcp@ci-build, workflow `ci-build.yaml`, runs 42-45.
#
# Triggers: content commits are excluded. `publish` touches kb/, raw/ and work/
# and never the stack, so running the suite for them would be pure noise. The
# exclusions are deliberately literal rather than a `!**/CONTRACT.md` negation,
# whose support in Gitea's filter matching is unverified: every pattern here
# names content, so anything unanticipated still triggers CI. The list is
# repeated rather than shared through a YAML anchor for the same reason -
# GitHub's parser rejects anchors outright, and Gitea's is not documented to
# accept them. `kb/CONTRACT.md` is absent on purpose: it is a stack file that
# happens to live under a content directory, and it must keep its CI.
#
# That the filter works is now observed, not assumed (Gitea issue #11): commit
# f916376 published only kb/ and raw/ paths and produced no run at all, while
# the stack commits on either side of it (adfa220, 40adbb7) each produced two.
# Gitea evaluates these patterns the way GitHub does. Do not re-derive this.
name: CI
on:
push:
branches: [main]
paths-ignore:
- 'kb/*/**'
- 'kb/index.md'
- 'kb/log.md'
- 'kb/provenance.md'
- 'raw/*/**'
- 'work/*/**'
- 'reports/*/**'
pull_request:
branches: [main]
paths-ignore:
- 'kb/*/**'
- 'kb/index.md'
- 'kb/log.md'
- 'kb/provenance.md'
- 'raw/*/**'
- 'work/*/**'
- 'reports/*/**'
workflow_dispatch:
jobs:
verify:
runs-on: linux-docker
container:
image: debian:trixie-slim
env:
# Scope the Iteration Budget Gate to this run instead of letting it fall
# back to the parent PID, and keep the trace out of the checkout so the
# working tree stays clean for the ignore-rule checks.
WIKITOOL_SESSION_ID: ci-${{ github.run_id }}
WIKI_TRACE_DIR: /tmp/wikitool-trace
DIST_DIR: /tmp/dist
steps:
- name: System dependencies
# `nodejs` is not for us - it is what act_runner needs to execute the
# JavaScript action in the next step. It has to be installed before the
# checkout, which is why this step comes first.
run: |
set -eu
apt-get update -qq
apt-get install -y --no-install-recommends \
python3 python3-venv git nodejs ripgrep ca-certificates
rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v7
with:
# The version gate diffs against the pushed range's base, so the
# shallow default clone is not enough.
fetch-depth: 0
- name: Tool environment
run: |
set -eu
git config --global --add safe.directory "$GITHUB_WORKSPACE"
python3 -m venv tools/.venv
tools/.venv/bin/pip install --quiet --upgrade pip
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
# pytest-cov is CI-only: tools/requirements.txt describes what an
# *instance* needs at runtime and ships with `dist export`, and an
# instance does not measure this suite. Installed beside pytest for
# the same reason pytest itself is.
tools/.venv/bin/pip install --quiet pytest pytest-cov
# The MCP server's dependency is optional for an instance but not for
# CI: its tests skip without it, and a skipped golden test is exactly
# how the server's output and the CLI's would drift apart unnoticed.
tools/.venv/bin/pip install --quiet -r tools/requirements-mcp.txt
- name: Tests
# Not run with WIKI_TRACE=0: two telemetry tests assert that a trace is
# written, and disabling the emitter globally fails them. The suite
# redirects WIKI_TRACE_DIR per test on its own.
#
# One run, not two. This job used to be the only place the suite met a
# machine with no global git configuration, which is how Gitea #8 was
# found - two tests that silently read the developer's `git config
# user.name`. That hole is now closed in the suite itself: the autouse
# `hermetic_environment` fixture gives every test an empty HOME, no
# git configuration and none of the tool's own environment, so this
# container is no longer a special environment worth a second run.
# See instructions/dev/testing-conventions.md.
#
# Coverage is measured and enforced at a floor of 85% against a measured
# 87.0% - `fail_under` in tools/.coveragerc, not a flag here, so the
# number sits next to the reasoning that produced it. It was set only
# after the number had been watched across 38 runs (Gitea #10, closed).
# A red suite from this floor means coverage actually fell; the two
# points of headroom already absorb a new thin Typer wrapper.
run: |
set -eu
cd tools
.venv/bin/python -m pytest -q \
--cov --cov-report=term --cov-report=xml --cov-report=html
- name: Coverage report
# `always()`: a red suite is exactly when the per-module numbers are
# worth reading, and the upload must not disappear with the failure.
# v3, not v4 - v4 is restricted on this Gitea instance; v3 is what is
# proven here (torben/gitea-mcp@ci-build, ci-build.yaml, runs
# 42-45).
#
# The artifact is downloadable from the run page, but the Actions
# artifact REST endpoints report `total_count: 0` for it - v3 writes
# through the older artifact API, which those endpoints do not read.
# An empty list is not a failed upload. See EVALS.md § "How much of the
# stack the suite reaches"; do not re-derive this.
if: always()
uses: actions/upload-artifact@v3
with:
name: coverage-${{ github.run_id }}
path: |
tools/coverage.xml
tools/htmlcov/
retention-days: 14
- name: Verify the development tree
run: |
set -eu
tools/wikitool docs verify
tools/wikitool instructions verify
tools/wikitool lint --fail-on-error
- name: Version gate
# A stack change with no version bump cannot be released, because the
# release would carry a change nobody named. Scoped to what
# `dist export` actually ships as behaviour - prose docs and these
# workflows are not in it, and a typo fix should not force a bump.
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -eu
base="${BASE_SHA:-${BEFORE_SHA:-}}"
case "$base" in
""|0000000000000000000000000000000000000000)
echo "No base commit to compare against - skipping the version gate."
exit 0
;;
esac
if ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
echo "Base commit $base is not in this clone - skipping the version gate."
exit 0
fi
changed="$(git diff --name-only "$base" HEAD)"
stack="$(printf '%s\n' "$changed" \
| grep -E '^(tools/|types/|instructions/|AGENTS\.md$|[^/]+/CONTRACT\.md$)' || true)"
if [ -z "$stack" ]; then
echo "No stack paths touched - no version bump required."
exit 0
fi
if printf '%s\n' "$changed" | grep -qx 'VERSION'; then
echo "Stack changed, and VERSION moved to $(cat VERSION)."
exit 0
fi
echo "Stack paths changed without a VERSION bump:"
printf '%s\n' "$stack" | sed 's/^/ /'
echo ""
echo 'Fix: tools/wikitool version bump --patch --title "<what changed>"'
echo 'Then `docs verify` holds VERSION and CHANGES.md together.'
exit 1
- name: Export the distribution
run: tools/wikitool dist export "$DIST_DIR"
- name: The distribution works as a fresh instance
# Replays instructions/setup-instance.md end to end, minus its four
# interactive decision points. What this tests is the release artifact
# as an artifact: the documented path from an unpacked export to a
# verified instance. Running one `instructions verify` against the
# export would only have re-checked the file it just copied.
#
# Personalization is stubbed the same way the identity is: the real
# step interviews the user, so CI substitutes a fixed answer - here,
# the template minus its sentinel line. That is deliberately the
# cheapest thing `doctor`'s personalization check accepts, because
# what is under test is that the export *carries* the templates, not
# what a person would write into them.
run: |
set -eu
cd "$DIST_DIR"
git init -q -b main
git config user.name "CI Instance"
git config user.email "ci@example.invalid"
for personal in USER SOUL; do
grep -v 'wikitool:template-unfilled' "$personal.md.template" > "$personal.md"
done
# The authoring conventions ride the same split one directory down,
# and are stubbed the same way: what is under test is that the export
# carries the templates and that `doctor`/`docs verify` accept an
# adopted one, not what a person would write into them. The collection
# contracts are adopted verbatim - the shipped text is a working
# default, unlike a personalization file.
grep -v 'wikitool:template-unfilled' kb/CONVENTIONS.md.template > kb/CONVENTIONS.md
for template in kb/*/COLLECTION.md.template types/*.template; do
cp "$template" "${template%.template}"
done
python3 -m venv tools/.venv
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
tools/wikitool instructions sync
tools/wikitool index rebuild
tools/wikitool sources rebuild-index
tools/wikitool doctor
tools/wikitool docs verify
tools/wikitool instructions verify
tools/wikitool lint --fail-on-error
tools/wikitool version show
# A fresh instance owes no migration: dist export declares its content
# version, so `status` must answer rather than ask for a baseline.
tools/wikitool migrate status
# Telemetry defaults off for a distributed instance (the
# .wikitool-release.json this export carries), so nothing above
# should have created a trace tree at all - see EVALS.md § "Whether
# it runs at all".
if [ -e reports/telemetry ]; then
echo "reports/telemetry/ exists in a fresh distributed instance - telemetry should default off"
exit 1
fi