Continuous Integration (CI)
This document describes the CI workflow defined in .github/workflows/ci.yml.
Triggers
The workflow runs on:
- push to
main - pull requests targeting
main
The workflow ignores documentation-only and temporary changes (README.md, AGENTS.md, docs/**, sandbox/**, tmp/**).
Jobs
1. action-regression-ci
Purpose: validate the Spring-hosted Custom GPT Action regression control client.
2. frontend-ci — application logic
Purpose: validate fixture-based frontend application behavior, source quality, and buildability (app/) without regenerating curriculum assets.
This includes the package/runtime adapters, learner-goal selection, GPT instruction limits, the deterministic ZIP primitive, ESLint, and npm run build:application.
build:application deliberately compiles and builds only the application against the committed runtime assets. The normal production command npm run build remains integrated and first prepares curriculum runtime assets.
3. graph-validation — curriculum
Purpose: validate skill graph/data integrity, generated artifacts, package contracts and self-tests, schemas, and deterministic release-model conformance.
The job contains the graph, view, placement, source, review, asset, schema, archive, and legacy-reference gates. It builds the package-only frontend and exercises the package builder, validator, provisioner, Readiness evaluator, and consumer through bounded self-tests, but it does not materialize or consume a real curriculum ZIP. Those production-boundary checks belong exclusively to the optional Package CI.
Pushes and pull requests always run both the application and curriculum jobs. The split is for ownership, diagnosis, and faster local iteration; it is not a path-based permission to skip a suite.
The local and hosted curriculum paths intentionally run the same scope/readiness and goal-source-rationale gates, so a local curriculum result cannot diverge merely because the two checklists differ.
Optional package, subject-export, and FWU-OWL workflow
.github/workflows/owl-ci.yml is the single optional Package CI. It runs weekly and can be started manually; it is not run for every push or pull request. Separate fresh runners cover the real 1.7-GB standalone JSON package and hermetic consumer, all real subject-export packages, and the Core-first FWU-OWL/roundtrip lane. Ordinary per-commit CI materializes no real curriculum package for any subject; it validates code, contracts, models, reviews, schemas, and committed status instead. SkillPilot's production runtime consumes the JSON curriculum package and does not require OWL.
The real 2.36-GB FWU-OWL release-conformance wrappers remain explicit release operations rather than ordinary CI. The optional workflow syntax-checks them but uses bounded contract tests and the focused roundtrip lane.
The required JSON release-model gate still verifies the exact FWU Core commit and file hash recorded as provenance in its build profile. That small checkout/trust check is not an OWL test: it creates no RDF, runs neither ROBOT nor HermiT, and performs no ontology validation or roundtrip.
The graph rule catalog is documented in:
docs/qa-ci/graph-validation-rules.mddocs/qa-ci/curriculum-quality-dashboard.mdfor the generated Workbench quality-status viewdocs/qa-ci/curriculum-quality-maturity-and-routes.mdfor detailedM0-M4, QA-scope, route, andCQR-*semantics
validate:graph supports two enforcement profiles:
- default:
GVR-*rules are strict (failing) - legacy-warn mode:
VALIDATE_GRAPH_STRICT_RULES=0 npm run validate:graph
Current scope note:
- this job validates the full authored landscapes as committed
- it additionally validates projected filtered learner graphs via
validate:view-filters - it additionally enforces the Hessen Mathematik Sek-I G8/G9 duration projection via
check:he-math-duration-projection; the check fails if G8/G9 evidence is missing, no G8/G9 differences are detected, or any canonical-duration-grade evidence link is not represented by authored year structure or duration-specificgoalPlacements - it additionally enforces visible curriculum source coverage for the canonical Mathematik/Physik projections via
quality:source-coverage-audit:check; global missing source-backed goals may remain as non-visible rollout backlog, but visible atomic goals must have direct source/mapping evidence or explicitly accepted surrogate evidence - it additionally checks registered generated Markdown status artifacts for the standard "do not edit manually" notice via
check:generated-doc-notices - it additionally checks that the generated QA status artifact registry is in sync with
app/scripts/generatedMarkdownNoticeRegistry.tsviacheck:generated-status-registry - it additionally checks local Markdown links under
docs/viacheck:docs-links - it additionally checks coverage for the main documentation indexes via
check:docs-indexes - it additionally validates explicit learner-facing composition-view files via
validate:composition-views - it additionally enforces all configured memory-card review ledgers via
quality:memory-card-review:check:all; in the dashboard, missingCQR-302review configuration blocksM6, whileM5remains the core curriculum QA level - it additionally enforces the Hessen Oberstufe retained-asset boundary: under
curricula/DE/Gymnasium/input/HE/abi, legacyGymnasiale_Oberstufepath strings may only remain inside allowlisted raw archival provenance files fromcurricula/DE/Gymnasium/input/HE/retained-asset-registry.json - it also enforces the Hessen Oberstufe repo-handoff boundary: the retired legacy tree must stay absent from the active repo, and active tooling/runtime/test surfaces may mention it only from the explicit allowlist in
curricula/DE/Gymnasium/provenance/hessen-upper-secondary-retirement-registry.json - it validates the Hessen 2026 chemistry exam pipeline artifacts (
slot_matrix.json,coverage_requirements.json,task_bank.json, and source-landscape release anchors) - it now also enforces the Hessen Sek-I retained-asset boundary: under
curricula/DE/Gymnasium/input/HE/lower-secondary, legacyGymnasium_9_Mittelstufepath strings are forbidden in retained operational archive content - it also enforces the Hessen Sek-I repo-handoff boundary: active tooling/runtime/test surfaces may mention the lower-secondary legacy tree only from the explicit allowlist in
curricula/DE/Gymnasium/provenance/hessen-lower-secondary-retirement-registry.json - the current CI scope for
validate:view-filtersis the reviewed canonical DE Gymnasium set (Mathematik,Physik,Chemie,Biologie,Informatik,Deutsch,Englisch,Französisch,Griechisch,Chinesisch,Geschichte,Politik und Wirtschaft,Musik,Latein,Spanisch,Wirtschaft,Overview) - the validator can be widened locally via
APPLICABILITY_VALIDATION_SCOPE=all npm run validate:view-filters, but CI does not enforce that broader scope yet - reviewed residual
APV-201warning cases are tracked indocs/qa-ci/applicability-accepted-warnings.jsonand are reported as accepted warnings, not active warnings;APV-202is reported as a diagnostic finding, not as warning debt
Optional local status snapshot:
cd app
npm run quality:curriculum-status
This writes docs/qa-ci/status/curriculum-quality-status.json and .md, which are read by the local Workbench route /quality-dashboard.
Optional MEM/FWU endpoint review snapshot:
cd app
npm run quality:mem-sparql-consistency
This writes the MEM audit and review-queue artifacts under docs/qa-ci/status/. It is not part of the blocking CI workflow because the MEM endpoint is external and currently exposes concrete curriculum data only for some configured jurisdictions. See docs/qa-ci/mem-sparql-consistency-runbook.md for the operational workflow.
4. backend-ci — application logic
Purpose: validate backend (backend/) via Gradle checks.
Steps:
./gradlew check
Status checks
All CI jobs must pass for a successful workflow run.
Run CI locally
From repository root:
./run_ci.sh
./run_ci.sh all
./run_ci.sh application
./run_ci.sh curriculum
./run_ci.sh package
./run_ci.sh owl
./run_ci.sh full
With no argument, all remains the default required repository gate. The modes are:
application: Custom GPT Action regression, fixture-based frontend application logic, GPT instruction limits, lint, application-only build, and isolated Gradle backend checks.curriculum: graph/data/review/asset gates, schemas, and deterministic JSON release-model conformance; it does not materialize the real 1.7-GB ZIP.package: optional real standalone JSON ZIP, independent package validation, secure provisioning/activation, Readiness evaluation, and the hash-bound hermetic package consumer.owl: optional FWU-OWL builder/contracts, reverse-compiler tests, semantic MEM/FWU roundtrip, and OWL 2 DL/HermiT validation.all: every required application and curriculum check exactly once, sharing dependency and tool setup where possible; package materialization and OWL are intentionally excluded.full:allplus the optional real package and OWL checks.
Unknown or multiple suite arguments fail with exit code 2. Do not infer the required suite from changed paths: cross-cutting runtime changes must still finish with ./run_ci.sh or ./run_ci.sh all. Use package for package/provisioning/consumer changes and owl for ontology or roundtrip changes; full runs both optional lanes.
The real package, reproducible subject exports, and OWL/roundtrip checks share the optional scheduled/manual GitHub workflow. Bounded builder, validator, provisioner, Readiness, consumer, and generated-status checks remain required per commit, while multi-gigabyte production-boundary exercises are kept out of ordinary commit CI.