Development and verification strategy¶
AgentFEM uses layered verification. The goal is fast feedback during an edit and broad evidence before shared code or a release changes—not blindly running the most expensive command after every keystroke.
Validation layers¶
Two-way thermoelastic prototype (2026-10-08)¶
The bounded experiment and its zero-field fixes passed 174 related serial tests, with one rank-divergence test intentionally skipped in serial. The prototype and prepared-linear modules passed 14 tests per rank with two MPICH processes. Related coverage includes projection, zero thermal expansion, prepared boundary reuse, first/second-order lifecycle, architecture and docs; this is not a full-release regression.
python -m pytest -q tests/test_thermoelastic_coupling_prototype.py tests/test_prepared_linear_problem.py tests/test_thermoelastic_semantics.py
agentfem mpi-run -n 2 --timeout 120 -- python -m pytest -q tests/test_thermoelastic_coupling_prototype.py tests/test_prepared_linear_problem.py
PYTHONPATH=src python tools/benchmark_thermoelastic_coupling.py --nonuniform --output outputs/coupling-prototype.json
The CLI result retains input fingerprints, tolerances, residuals, reference errors and assembly counts without automatically promoting trust. See coupling design for the identities and remaining gates.
Sequential thermal--mechanical sprint (2026-10-08)¶
Focused acceptance: 71 serial tests across field transfer, public recovery example, engineering workflows, State, thermoelastic semantics and documentation. The field-transfer and example modules additionally pass 20 tests per rank on two MPICH processes. A separate public-example run saved heat on one rank and restored on two ranks, reporting zero thermal solves during structural recovery. These are bounded workflow checks, not a full release acceptance.
python -m pytest -q tests/test_sequential_field_transfer.py tests/test_sequential_workflow_example.py tests/test_engineering_workflows.py tests/test_state_contract.py tests/test_thermoelastic_semantics.py tests/test_documentation.py
agentfem mpi-run -n 2 --timeout 180 -- python -m pytest -q tests/test_sequential_field_transfer.py tests/test_sequential_workflow_example.py
Use a new directory for the first command below, then reuse it only with the explicit recovery flag:
python examples/thermal_stress_wall_2d.py --smoke --output outputs/wall-recovery
agentfem mpi-run -n 2 --timeout 180 -- python examples/thermal_stress_wall_2d.py --smoke --resume-heat --output outputs/wall-recovery
The layered testing policy remains:
| Moment | Required evidence | Typical command |
|---|---|---|
| Inner development loop | Direct unit/interface tests for the changed owner | python -m pytest -q tests/test_extensions.py |
| Before committing | Related workflow tests, critical static analysis, misuse tests, and generated-asset checks | ruff check . --no-cache; python build_knowledge.py --check --check-imports; python build_docs.py --check |
| Before pushing a coherent code change | Related suites; complete serial for cross-cutting numerical changes | python -m pytest -q when the change can cross ownership boundaries |
| MPI-sensitive change | Relevant two-rank modules using the verified launcher and a bounded test budget | agentfem mpi-run -n 2 --timeout 600 -- python -m pytest ... |
Test-only or orchestration pull request and main |
Changed test modules, a built-wheel solve, and serial/two-rank smoke | GitHub Actions targeted tier |
Core numerical pull request and main |
Wheel installation, owner-selected serial/MPI suites, affected portable-restart drivers, smoke tests, and documentation | GitHub Actions core tier |
Documentation-only pull request and main |
Strict generated-document and site build; no FEniCSx or PyTorch environment rebuild | GitHub Actions docs tier |
| Release candidate/tag | All preceding checks plus distribution inspection and installed-wheel release smoke | python release_gate.py --dist dist --smoke |
An extension promotion additionally requires two independently built wheels.
extension_gate.py installs them into an isolated prefix, executes a declared
project outside the checkout, verifies the sealed result, and proves that the
installed core tree was unchanged. Simulated entry-point tests do not replace
this evidence.
The 0.4 foundation certificate is an aggregation step, not another expensive
test matrix. On the release tier, foundation_gate.py validates the installed
smoke and external-provider artifacts after the serial and representative MPI
stages have passed, then binds those stages to the clean source commit and
candidate wheel digest. Omitting any required stage, changing the wheel, or
mixing extension evidence from another commit fails closed. The resulting
record is consumed with promotion_gate.py --target 0.4-foundation.
Transient lifecycle maintenance¶
The post-0.4 maintenance slice uses these focused checks in fenicsx-env:
python -m pytest -q tests/test_operator_lifecycle_ledger.py \
tests/test_first_order_operator_lifecycle.py tests/test_implicit_dynamics_lifecycle.py \
tests/test_transient_restart.py tests/test_sprint_transient_rollback.py tests/test_time_inputs.py
agentfem mpi-run -n 2 --timeout 600 -- python -m pytest -q \
tests/test_operator_lifecycle_ledger.py tests/test_first_order_operator_lifecycle.py \
tests/test_implicit_dynamics_lifecycle.py -k 'collective or live_transferred'
PYTHONPATH=src python tools/benchmark_operator_identity.py
The measurement compares the former Python-int tuple snapshot with an exact array-byte snapshot, including equality comparison. It does not measure a complete solve or promise an end-to-end speedup. On the development macOS ARM64 host (Python 3.11.15, NumPy 2.4.6), seven repeats of 100 checks at 100,000 boundary indices measured approximately 3.79 ms versus 0.012 ms per check; single-snapshot peak Python allocation was 4.12 MB versus 0.40 MB. Reproduce locally rather than enforcing wall-clock thresholds in CI. Both paths remain linear in the number of boundary indices; this is not a constant-time cache.
The original 0.4.0 code was independently replayed for a four-increment heat and dynamics run, releasing the prepared system after increment two. It reported one matrix and two solves instead of the actual two matrices and four solves. The shared ledger retains totals across prepared generations. Checkpoint restoration starts a new execution-cost ledger, not fabricated timings from the previous process. Integration equations and accepted-state history remain owned by their existing Procedures.
Live-input tests additionally compare both RHS-only updates and operator updates with the independent implicit-Euler recurrence for a spatially uniform linear first-order problem. They check the computed field and assembly counts in serial and MPI, not only the declared capability metadata. This protects a coupling prerequisite; it is not a validation of a complete coupled solver.
Source and installed-wheel evidence are separate¶
The repository uses the standard src/agentfem/ package layout. Pytest is
configured with pythonpath = ["src"], so python -m pytest exercises the
current checkout rather than an older agentfem already present in the
environment. The release gate follows the opposite rule: it installs
the candidate wheel into an isolated target, rejects a source-checkout import,
compares every packaged runtime file with the candidate source, and then runs
the flagship workflows and installed project templates. A release therefore
needs both source evidence and installed-artifact evidence.
Build a release candidate from a clean packaging workspace. Setuptools may
reuse files under an old local build/ directory even after those files have
left the source tree. CI starts from a fresh checkout; a local maintainer should
remove or archive stale build/ and dist/ directories before python -m
build. The release gate's source-to-wheel digest comparison is the final guard:
an extra or stale runtime file is a release failure, even when the version
number is correct.
Targeted tests answer “did this edit break its owner?” Full tests answer “did this apparently local edit violate another public contract?” Both are needed.
For an explicit source-tree check, keep the repository root as the working
directory and prepend its src directory:
PYTHONPATH="$(pwd)/src" python -c \
'import agentfem; print(agentfem.__file__)'
PYTHONPATH="$(pwd)/src" python -m pytest -q
The printed path must point to the current checkout. Release CI instead builds and force-installs the candidate wheel before testing, intentionally verifying the artifact users receive.
Source evidence records two deliberately different hashes. The
package_tree_sha256 binds every packaged byte and remains the exact audit
identity. The scientific_runtime_sha256 excludes only
knowledge/benchmarks/**, whose records describe promotion state rather than
execute a numerical model. Promotion comparisons use the scientific runtime
identity while retaining every exact package identity. Consequently, changing
a solver, material, operator, knowledge card, schema, or any other packaged
resource invalidates the evidence; changing an archived benchmark declaration
from experimental to accepted does not create a circular demand to rerun the
calculation that justified that declaration.
promotion_gate.py is a source-checkout audit and therefore inserts the local
src/ directory itself. It also rejects an AgentFEM import from outside that
checkout. This prevents an installed older wheel from being paired with the
current Git commit in a promotion report. Installed-wheel acceptance remains a
separate G5 record rather than being inferred by the source audit.
Direct MPI driver scripts do not pass through pytest's pythonpath setting.
When they are used against an uninstalled checkout, prefix both serial and MPI
commands with the checkout parent explicitly, for example:
PYTHONPATH="$(pwd)/src" agentfem mpi-run -n 2 --timeout 600 -- \
python tests/portable_inelastic_step_driver.py write /tmp/agentfem-step
The timeout is a test-lifecycle guard, not a solver convergence parameter. If
an MPI rank diverges before a collective while another rank waits inside it,
the launcher and every child rank are terminated as one process group and the
CLI reports AFM-MPI-TIMEOUT. Do not remove the guard merely to make a stuck
test appear busy; first identify the mismatched rank path. Long production
simulations remain unlimited unless the caller explicitly supplies a timeout.
Release CI deliberately omits this prefix after force-installing the candidate wheel; those same drivers then provide installed-artifact evidence.
Change-aware CI tiers¶
Cross-module coupling is high—changes to Model, providers, output, mesh
identity, or checkpointing can affect many workflows. Known core owners select
their serial suites, two-rank suites, and any executable cross-rank restart
drivers explicitly. An unmapped core owner escalates to the complete release
gate instead of inventing a partial test set. Test corrections and product
orchestration changes run the affected test files against a freshly built
wheel, followed by serial and two-rank solver smoke tests. Documentation
changes build the strict site without creating a FEniCSx environment. PyTorch
is installed only for learning changes or a release gate.
The always-running classifier reports one explicit tier:
| Tier | Automatic scope | Evidence |
|---|---|---|
docs |
Documentation and generated site inputs only | Generated entrypoints and strict site build |
targeted |
Tests, examples, integrations, CI, CLI, campaign, dataset, or surrogate orchestration | Built wheel, affected tests, serial and two-rank smoke |
core |
Mapped FEM formulation, operator, constitutive, state, result, solver, or MPI owners | Owner-selected serial/MPI suites, portable drivers, and installed-wheel smoke |
release |
Release identity/runtime inputs or explicit dispatch | Core gate plus release smoke, promotion audit, and retained artifacts |
Unknown repository paths fail safe to core; unknown src/agentfem owners
escalate to release. A manual dispatch selects the requested minimum tier and
defaults to release. New commits cancel superseded development runs;
immutable tag and release evidence is never replaced this way.
Developers should still begin with the smallest relevant tests. Re-running the entire environment and MPI matrix after every one-line edit wastes time and delays diagnosis. Related edits should be collected into one reviewable commit and pushed after local evidence is green; a remote quota or infrastructure failure is recorded once rather than retried until green.
CI resource policy¶
Validation depth follows the evidence claim, not the number of edited files:
- use targeted local tests while developing;
- push coherent changes rather than each intermediate edit;
- choose the remote tier from scientific impact, with unknown changes failing safe to the core gate;
- reserve hosted platform matrices, installers, and expensive scientific benchmarks for tags, explicit dispatch, or scheduled evidence;
- let a newer development commit cancel an obsolete run;
- reuse evidence only when it remains bound to the exact code and artifact digest that produced it.
Standard hosted runners are free for public repositories and metered for private repositories, but queue time, compute, storage, and reviewer attention are still finite engineering resources. Cost control must not weaken a release claim; equally, repeated computation that proves no new claim is not verification.
When the suite grows¶
Introduce registered pytest markers only when runtime measurements justify
them, for example unit, fem, mpi, external, and release. Markers must
describe evidence or runtime requirements, not vague importance. The fast gate
must never become a permanently weaker alternative to the complete gate.
A mature schedule is:
- targeted tests on every edit;
- fast deterministic gate on every commit;
- full serial and affected MPI tests for core numerical pull requests;
- complete platform/MPI/optional-dependency evidence for releases and scheduled audits;
- external-code and large-mesh benchmarks on a scheduled or release gate.
If a check is required by branch protection, prefer a workflow that always reports a conclusion. GitHub documents that an entire workflow skipped by path filters can leave a required check pending. Job-level conditions or a small always-running decision job are safer when selective CI is eventually needed.
Failure policy¶
- A failed targeted test blocks the edit immediately.
- A failed full test is not dismissed because unrelated targeted tests pass.
- Flaky numerical tests should be diagnosed, not retried until green.
- Golden regression, external-code comparison, mesh/time convergence, and experimental validation remain different evidence classes.
- Optional integrations are tested in isolated jobs so a core developer does not need every dependency locally.
This strategy keeps the development loop efficient without weakening the scientific claims attached to a release.
Static-analysis adoption¶
The first Ruff gate deliberately checks correctness-sensitive rules: syntax, undefined names, invalid control flow, loop-variable capture, and mutable function-call defaults. It does not reformat the historical repository or rewrite third-party reference scripts. Broader style rules may be adopted module by module only when their review cost is justified.