Compliance & Traceability

Test Traceability for Aerospace (AS9100)

Build AS9100 test traceability for aerospace: what records auditors ask for, and how to capture them automatically with a TofuPilot procedure.

JJulien Buteau
intermediate8 min readMarch 14, 2026

AS9100 is the aerospace quality management standard, built on ISO 9001 with additional requirements for configuration management, risk, and traceability. For a test engineer, the practical question is narrower: when an auditor picks a shipped serial number, can you show what was tested, with which equipment, against which revision of the limits, by whom, and when? This guide covers the records AS9100 expects from test, and how to capture them without manual paperwork.

What AS9100 Asks of Test Data

Clause areaWhat it means for testRecord you must produce
Configuration management (8.1.2)The test that ran matches the released revisionProcedure version recorded on every run
Traceability (8.5.2)Every unit is identifiable through the processSerial number on every run, linked to sub-assemblies
Monitoring equipment (7.1.5)Instruments were in calibration when usedInstrument ID and calibration due date per run
Nonconforming output (8.7)Failures were detected, segregated, dispositionedFailed runs retained, with disposition
Retention of records (7.5.3)Records survive and stay readableImmutable run history, exportable
Competence (7.2)The operator was qualifiedOperator identity on the run

The theme is that a pass/fail verdict is not a record. The record is the measured value, its limits, the revision of those limits, the equipment, the operator and the timestamp.

Step 1: Version the Procedure, Not Just the Code

AS9100 configuration management cares that the test which ran is the released one. Put an explicit version in the procedure and let every run carry it.

procedure.yaml
32 lines
name: LRU Acceptance Testversion: 2.3.0description: Acceptance test for the 28V power conditioning LRU, per ATP-4417 rev Cunit:  serial_number:    default_value: "LRU000001"  part_number:    default_value: "LRU-4417"plugs:  - name: dmm    python: plugs.dmm:Multimetermain:  - name: Output Voltage    python: phases.output_voltage    measurements:      - name: Output Voltage        unit: V        description: Per ATP-4417 rev C section 4.2        validators:          - operator: ">="            expected_value: 27.5          - operator: "<="            expected_value: 28.5      - name: Ripple        unit: mV        description: Per ATP-4417 rev C section 4.3        validators:          - operator: "<="            expected_value: 150
phases/output_voltage.py
def output_voltage(measurements, dmm):    measurements.output_voltage = dmm.measure_dc_voltage()    measurements.ripple = dmm.measure_ripple_mv()
plugs/dmm.py
class Multimeter:    def measure_dc_voltage(self) -> float:        return 28.02  # Replace with a real instrument read    def measure_ripple_mv(self) -> float:        return 96.0  # Replace with a real instrument read

The version field is recorded with every run, so results taken before a limit change stay interpretable afterwards. The description on each measurement is where the acceptance test procedure paragraph goes, so the auditor's document and your data line up without a translation step.

Deployments add the other half: the CLI records the exact commit that ran on the station, so "which code produced this result" has an answer that does not depend on anyone's memory.

Step 2: Record the Equipment and Its Calibration

Clause 7.1.5 wants evidence that monitoring equipment was fit for use. Read the identity from the instrument rather than typing it, and record the calibration due date alongside it.

phases/record_equipment.py
def record_equipment(run, dmm):    """Capture instrument identity and calibration status on the run."""    run.metadata["dmm_id"] = dmm.identify()    run.metadata["dmm_cal_due"] = dmm.calibration_due()
plugs/dmm.py
class Multimeter:    def identify(self) -> str:        return "Keysight 34461A,MY59001234"  # Replace with *IDN? query    def calibration_due(self) -> str:        return "2027-04-18"  # Replace with your calibration system lookup

Making the calibration date a measurement rather than metadata turns it into a gate, which is stronger: a station running an out-of-calibration instrument fails the run instead of quietly producing records an auditor will later reject.

procedure.yaml
- name: Equipment Check    python: phases.equipment_check    measurements:      - name: Calibration Valid        description: Instrument calibration in date at time of test        validators:          - operator: "=="            expected_value: true
phases/equipment_check.py
from datetime import datedef equipment_check(measurements, dmm):    due = date.fromisoformat(dmm.calibration_due())    measurements.calibration_valid = due >= date.today()

Step 3: Identify the Operator

Competence (7.2) is about qualified people. The run needs to say who ran it. On a station, prompt for a badge scan as an operator input, and record it.

phases/record_operator.py
def record_operator(run, operator_badge):    run.metadata["operator_badge"] = operator_badge

For unattended or CI-driven runs there is no operator, and the honest record says so rather than inventing one.

Traceability (8.5.2) rarely stops at the top-level unit. An LRU contains boards, and a board contains a screened component lot. Model the child units so an auditor pulling a shipped serial can walk down, and a containment action can walk up from a suspect lot to every aircraft it reached.

See how to build full unit traceability for the sub-assembly setup.

Step 5: Keep the Failures

A common and expensive mistake is deleting or re-running over failed results to keep a yield number clean. AS9100 clause 8.7 expects nonconforming output to be identified and controlled, which means the failed run is evidence and must survive.

Keep the failing run, record the disposition, and let the retest be its own run under the same serial. The history then shows the failure, the decision and the outcome in order, which is exactly what an auditor is looking for.

Step 6: Run and Retain

run.sh
tofupilot run ./procedure.yaml --upload

Each run stores the measured values with their limits, the procedure version, the deployment commit, the equipment metadata, the operator, and timestamps. Records are retained and exportable, which covers 7.5.3 without a file server full of spreadsheets nobody can open in five years.

If You Already Run OpenHTF

Existing OpenHTF suites run natively on the TofuPilot CLI with no rewrite, and produce the same records. The equivalent of the version field and the equipment metadata looks like this:

lru_acceptance.py
25 lines
import openhtf as htffrom openhtf.util import unitsfrom tofupilot.openhtf import upload@htf.measures(    htf.Measurement("output_voltage_V")    .in_range(27.5, 28.5)    .with_units(units.VOLT)    .doc("Per ATP-4417 rev C section 4.2"),    htf.Measurement("ripple_mV")    .in_range(maximum=150)    .with_units(units.MILLIVOLT)    .doc("Per ATP-4417 rev C section 4.3"),)def phase_output(test):    test.measurements.output_voltage_V = 28.02    test.measurements.ripple_mV = 96.0    test.metadata["procedure_revision"] = "2.3.0"    test.metadata["dmm_id"] = "Keysight 34461A,MY59001234"test = htf.Test(phase_output, part_number="LRU-4417")test.add_output_callbacks(upload())test.execute(test_start=lambda: input("Scan serial: "))

Install with pip install "tofupilot[openhtf]", or skip the callback and run it with tofupilot run.

Audit Readiness Checklist

Question an auditor asksWhere the answer lives
What was tested on this serial?The run history for that unit
Against which limits?The measurements stored with the run, including limits
Which revision of the test?The procedure version and the deployment commit
With what equipment?Equipment metadata, or a calibration gate measurement
Was the equipment in calibration?The calibration measurement outcome
Who ran it?Operator metadata on the run
What happened to the failures?Retained failing runs plus the disposition and retest
What else used that component lot?Sub-assembly links from the child serial upward

Common Mistakes

MistakeConsequenceFix
Recording pass/fail onlyCannot show margin or drift to an auditorStore the measured value with its limits
Unversioned proceduresOld results become uninterpretable after a limit changeSet version and change it deliberately
Typing instrument IDs by handTranscription errors in the audit trailRead *IDN? from the instrument
Calibration tracked in a spreadsheetOut-of-cal equipment produces records you must scrapGate the run on a calibration measurement
Deleting failed runsNonconformance evidence destroyedRetain the failure, record the disposition
No sub-assembly linksContainment means scrapping everythingModel child units from day one

More Guides

Put this guide into practice