Fault-Model Traceability Matrix
June 9, 2026 ยท View on GitHub
When to read this: When changing high-risk timing semantics, reviewing a fault-model audit, or deciding whether a proposed fix belongs in runtime code, tests, docs, or a follow-up issue.
Fault-Model Traceability Matrix
This matrix is a maintainer traceability artifact for issue #309 under the release-readiness umbrella #302. It does not prescribe new runtime behavior. Use it to keep high-consequence timing failure modes connected to their current guards, evidence homes, and explicit support boundaries before editing the core stop/repair path, MPI descriptor exchange, or MPI+OpenMP summary code.
| Fault category | Trigger | User-visible symptom if broken | Existing guard | Test or doc home | Missing counterexample or non-goal |
|---|---|---|---|---|---|
| Strict nesting and repair | stop() names a timer that is not the current stack top. | Times are attributed to the wrong parent context, active stacks are corrupted, or repair creates timing gaps. | Strict mode leaves state unchanged; warn/repair modes use one captured timestamp, unwind to the target, stop it, and restart unwound timers in reverse. Repair-restarted timers intentionally resume timing without counting a new user start. | src/ftimer_core.F90; tests/test_nesting.pf; docs/semantics.md "Mismatch Handling" and "Local Summary Contract". | Non-goal: do not simplify the stop/repair path without a concrete bug or characterization gap. #314 and #316 should preserve this guardrail. |
| Callback suppression during repair | Warn/repair mismatch recovery unwinds and restarts internal activations. | External observers see phantom start/stop events and infer user work that did not happen. | Callback hooks fire only for normal user-visible start/stop events; internal repair transitions are callback-silent. | src/ftimer_core.F90; tests/test_callbacks.pf; docs/semantics.md "Callback Contract"; docs/design.md callback design note. | Non-goal: callbacks are lightweight intra-run hooks, not a stable profiler-backend event stream or semantic id map. |
| Active local snapshots versus stopped-run reductions | A caller requests a local summary while timers are active, or requests MPI/OpenMP summaries before stopping all work. | A final report silently includes partial active intervals, or a collective summary mixes incomplete rank/lane data. | Local summaries are live snapshots with has_active_timers and per-entry is_active; strict MPI, sparse MPI, local OpenMP, strict hybrid, and sparse hybrid summaries are stopped-run preflight points that reject active timers. | docs/semantics.md "Local Summary Contract", "MPI Guarantees", and OpenMP summary sections; tests/test_summary.pf; tests/mpi/test_mpi_summary.pf; tests/test_openmp_summary_smoke.F90; tests/test_openmp_mpi_summary_smoke.F90. | Non-goal: local get_summary() is not changed into a stopped-run-only API. |
| Nonmonotonic custom or backend clocks | An injected clock or processor clock moves backward, wraps, or mixes epochs after data exists. | Negative elapsed time or misleading % Total denominators appear rather than a hidden corrected interval. | Clock changes are rejected after timing data exists; successful no-data clock changes restart the summary window. fTimer assumes monotonic clocks and does not clamp backward movement or synthesize corrected elapsed time. | src/ftimer_core.F90; docs/semantics.md "Default serial system_clock assumptions" and "Clock Configuration Contract"; README.md operational notes. | Missing counterexample: no dedicated behavioral test asserts that a deliberately backward mock clock remains visible as negative elapsed time; add only through a follow-up if maintainers want that contract pinned. |
| MPI descriptor mismatch | Communicator ranks materialize different timer names, parent stacks, or context trees for strict MPI summary/report calls. | MPI reductions combine unrelated entries, hang, or return plausible but wrong global statistics. | Strict MPI summary performs descriptor-hash preflight across the init communicator before reducing timing fields and returns FTIMER_ERR_MPI_INCON on mismatch. | src/ftimer_mpi.F90; tests/mpi/test_mpi_consistency.pf; tests/mpi/test_mpi_summary.pf; docs/history/mpi-descriptor-preflight-decision.md; docs/semantics.md "MPI Guarantees". | Non-goal: do not replace or refactor the descriptor exchange from #309. #314/#316 must not weaken this guardrail. |
| MPI communicator lifetime and agreement | A timer captures a freed subcommunicator, uses MPI-enabled fTimer outside MPI_Init/MPI_Finalize, or different rank groups enter different communicators. | Undefined MPI behavior or a hang instead of an fTimer status. | Public API captures mpi_f08 type(MPI_Comm) handles as non-owning borrowed communicators and documents consistent-communicator lifetime as caller-owned. | docs/semantics.md "MPI Guarantees" and "MPI lifecycle and communicator ownership"; docs/troubleshooting.md; tests/check_mpi_lifecycle_contract_docs.cmake; README.md operational notes. | Non-goal: no cross-communicator rescue rendezvous or communicator ownership/duplication is promised. |
| Sparse participation semantics | Rank- or lane-conditional timers are summarized through sparse MPI or sparse hybrid APIs. | Absent contributors are mistaken for present zero work, or strict APIs silently relax. | Sparse APIs are separate result/report/CSV families with explicit participation and missing counts; statistics are over participating contributors only. Strict APIs keep identical-descriptor requirements. | src/ftimer_mpi.F90; src/ftimer_openmp.F90; tests/mpi/test_mpi_summary.pf; tests/test_openmp_mpi_summary_smoke.F90; docs/history/mpi-sparse-summary-decision.md; docs/semantics.md sparse MPI and sparse hybrid sections. | Non-goal: no all-rank zero-filled or amortized sparse fields are part of the current result model. |
| Legacy OpenMP worker no-op compatibility | Existing ftimer or ftimer_core APIs are called from non-master OpenMP threads when FTIMER_USE_OPENMP=ON. | Worker-only timers vanish, call counts are master-only, or users infer per-thread timing that was never recorded. | Guarded core operations run only on thread 0; worker calls are skipped before validation, emit no stderr, and leave caller-provided ierr unchanged. | src/ftimer_core.F90; tests/test_openmp_guards.pf; examples/openmp_example.F90; docs/openmp-timing-modes.md; docs/semantics.md "Consequences for timing data". | Non-goal: this compatibility path is not general thread safety or true worker timing; use ftimer_openmp_t for that. |
| Strict versus sparse hybrid lane/rank mismatches | MPI+OpenMP ranks or eligible lanes differ in descriptors, domains, lane participation, or mixed OpenMP epoch team-size precision. | Missing lane/rank data is zero-filled under a strict result, or sparse summaries lose explicit missing-contributor meaning. | Strict hybrid summaries reject descriptor, eligible-lane, and unknown missing-lane precision mismatches with FTIMER_ERR_MPI_INCON; sparse hybrid summaries are a separate union API with rank/lane participation metadata, participating-sample statistics, and explicit unknown missing-sample flags. | src/ftimer_openmp.F90; tests/test_openmp_mpi_summary_smoke.F90; tests/test_openmp_mpi_summary_3rank_smoke.F90; tests/check_openmp_mpi_union_descriptor_contract.cmake; docs/openmp-timing-modes.md; docs/semantics.md strict and sparse hybrid sections. | Non-goal: do not replace sparse participation metadata with all-rank/lane zero-filled fields; mixed OpenMP epochs keep aggregate eligible counts but must expose unknown missing-lane precision. |
| Scoped-guard ownership versus public ids | A guard activation is manually stopped, repaired away, copied, or confused with a cached lookup id. | Guard finalization stops the wrong activation or hides a mismatch. | Guards store internal segment indexes plus activation tokens; public cached lookup ids are opaque handles. guard%stop(ierr) may stop only the exact activation it started. | src/ftimer_core.F90; src/ftimer_types.F90; tests/test_scoped_guard.pf; tests/test_oop_scoped_guard.pf; docs/semantics.md "Scoped Guard Contract"; README.md API notes. | Non-goal: ftimer_scope_id remains deferred; guard internals are unstable public-by-necessity, not downstream API. |
| Worker diagnostic draining paths | ftimer_openmp_t observes invalid worker calls and later reaches a serial lifecycle or summary/report call. | Diagnostics are lost, emitted out of order from workers, or double-reported on collectives. | Worker paths queue bounded diagnostics. Serial explicit-ierr draining returns the first status without stderr; omitted-ierr paths emit aggregate diagnostics, while collective MPI+OpenMP summary diagnostics are intentionally rank-gated when status will be surfaced elsewhere. | src/ftimer_openmp.F90; tests/test_openmp_api_diagnostics.F90; tests/test_openmp_api_ierr_silence.F90; tests/check_openmp_api_diagnostics.cmake; tests/check_openmp_api_ierr_silence.cmake; docs/semantics.md OpenMP object contract. | Non-goal: do not unify explicit and silent diagnostic paths unless a concrete user-facing ambiguity is demonstrated. |
Maintenance Notes
- Treat this page as a map to existing evidence, not a replacement for
docs/semantics.md. - If a row exposes missing evidence, file a focused follow-up before adding runtime changes. #317 owns mixed OpenMP epoch / eligible-lane interpretation evidence and records that those aggregate eligible counts carry explicit unknown missing-lane precision when team sizes differ across epochs.
- Keep #314 and #316 refactors documentation-preserving: they may reduce duplication, but should not weaken the core stop/repair or descriptor preflight guardrails captured above.