A parsed maintenance record is only defensible if every handoff between pipeline stages carries a verifiable, tamper-evident signature. This page shows how to place cryptographic signing checkpoints at each stage boundary of the ingestion graph so that a release from OCR to extraction, or from normalization to the traceability ledger, becomes non-repudiable and satisfies the FAA 14 CFR § 43.9 intent that a signature attests to the specific work recorded. It belongs to the broader effort of Mapping FAA Part-145 Obligations to Ingestion Pipeline Stages, which allocates each regulatory duty to a concrete processing stage.
Prerequisites & inputs
Non-repudiation here means that the party who released a stage output cannot later deny having done so, and that no intermediary can alter the payload without detection. Achieving this in a distributed ingestion graph requires three inputs. First, a stable per-stage payload: the exact bytes emitted by a stage, hashed with SHA-256 before anything downstream touches them. Second, a signing identity bound to a certifying agent — either a human engineer’s credential or a service account operating under Part-145 controls per § 145.219 recordkeeping obligations. Third, an asymmetric key pair per signer; this implementation uses Ed25519 because its deterministic signatures and short keys suit high-throughput MRO batch runs.
The checkpoint model assumes upstream stages have already produced structurally valid JSON. It does not perform schema validation or content correction; it only attests to what was released and by whom. A worked scenario in this page uses German-registered aircraft D-AIMA, a cabin-pressure controller HTP-4471-02, and ATA chapter 21-31 (pressurization control) to keep the example concrete.
Signed stage-boundary checkpoints
Each stage output is hashed, wrapped in a signed checkpoint, and the next stage refuses to begin until it verifies the upstream signature. Chaining each checkpoint to the digest of the previous one turns the graph into an append-only lineage that mirrors the electronic-recordkeeping expectations of AC 120-78B.
Step-by-step implementation
- Freeze the payload. Serialize the stage output deterministically and compute its SHA-256 digest. Never sign mutable, unsorted structures — the verifier must reconstruct identical bytes.
- Assemble the checkpoint. Bind the stage name, payload digest, signer identity, an ISO 8601 UTC timestamp, and the digest of the prior checkpoint into one object.
- Sign the canonical bytes. Serialize the checkpoint fields (excluding the signature) with sorted keys and compact separators, then sign with the private key.
- Verify before consuming. The downstream stage recomputes the payload digest, rebuilds the signing bytes, and calls
verifywith the signer’s public key. Any mismatch quarantines the record.
from __future__ import annotations
import base64
import hashlib
import json
import logging
from datetime import datetime, timezone
from typing import Any, Optional
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
)
from pydantic import BaseModel, ConfigDict, Field, field_validator
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger("mro.checkpoint")
class SignedCheckpoint(BaseModel):
"""Non-repudiable handoff record at an ingestion-stage boundary.
Carries the FAA 14 CFR § 43.9 signature intent: the signer attests to the
exact payload released at this stage, not merely that some work occurred.
"""
model_config = ConfigDict(strict=True, extra="forbid")
stage: str = Field(min_length=3, max_length=64)
payload_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
signer_id: str = Field(min_length=3, max_length=64)
signed_at: datetime
signature_b64: str = ""
prev_checkpoint_sha256: Optional[str] = Field(default=None, pattern=r"^[0-9a-f]{64}$")
@field_validator("signed_at", mode="before")
@classmethod
def enforce_utc(cls, v: Any) -> datetime:
if isinstance(v, str):
return datetime.fromisoformat(v.replace("Z", "+00:00")).astimezone(timezone.utc)
if isinstance(v, datetime):
return v.astimezone(timezone.utc)
raise ValueError("signed_at must be an ISO 8601 timestamp")
def signing_bytes(self) -> bytes:
# Deterministic serialization is mandatory: the verifier must rebuild
# exactly these bytes. Sorted keys + compact separators guarantee it.
material = {
"stage": self.stage,
"payload_sha256": self.payload_sha256,
"signer_id": self.signer_id,
"signed_at": self.signed_at.isoformat(),
"prev_checkpoint_sha256": self.prev_checkpoint_sha256,
}
return json.dumps(material, sort_keys=True, separators=(",", ":")).encode("utf-8")
def sha256_hex(payload: bytes) -> str:
return hashlib.sha256(payload).hexdigest()
def sign_stage_output(
stage: str,
payload: bytes,
signer_id: str,
private_key: Ed25519PrivateKey,
prev_checkpoint_sha256: Optional[str] = None,
) -> SignedCheckpoint:
checkpoint = SignedCheckpoint(
stage=stage,
payload_sha256=sha256_hex(payload),
signer_id=signer_id,
signed_at=datetime.now(timezone.utc),
prev_checkpoint_sha256=prev_checkpoint_sha256,
)
signature = private_key.sign(checkpoint.signing_bytes())
signed = checkpoint.model_copy(
update={"signature_b64": base64.b64encode(signature).decode("ascii")}
)
logger.info("signed stage=%s payload=%s signer=%s", stage, signed.payload_sha256[:12], signer_id)
return signed
def verify_upstream(
checkpoint: SignedCheckpoint, payload: bytes, public_key: Ed25519PublicKey
) -> bool:
if sha256_hex(payload) != checkpoint.payload_sha256:
logger.error("payload digest mismatch at stage=%s", checkpoint.stage)
return False
try:
public_key.verify(base64.b64decode(checkpoint.signature_b64), checkpoint.signing_bytes())
except InvalidSignature:
logger.error("bad signature stage=%s signer=%s", checkpoint.stage, checkpoint.signer_id)
return False
logger.info("verified upstream stage=%s signer=%s", checkpoint.stage, checkpoint.signer_id)
return True
Worked example
Consider the extraction stage releasing a defect record for D-AIMA: removal of cabin-pressure controller HTP-4471-02, serial SN-88213, under ATA 21-31. The stage serializes that record to payload, and sign_stage_output("field_extraction", payload, "CS-EASA-14477", priv_key) returns a checkpoint whose payload_sha256 begins 9f2c1a... and whose signature_b64 is a 88-character Ed25519 signature. The normalization stage then calls verify_upstream(checkpoint, payload, pub_key); because the bytes match, it logs verified upstream stage=field_extraction signer=CS-EASA-14477 and proceeds.
Now suppose a downstream process silently rewrites the serial to SN-88123. The re-serialized payload hashes to a different digest, verify_upstream returns False at the digest-comparison step before the signature is even checked, and the tampered record is quarantined. Because each checkpoint also stores prev_checkpoint_sha256, an auditor can walk the chain backward to the OCR release and confirm exactly which signer released which bytes. That reconstructed chain is precisely what a reviewer needs when Replaying Pipeline Transformations for Audit demands proof that no stage mutated a record off the record.
Edge cases & compliance gotchas
- Clock skew across signers. If two service accounts on different hosts drift, checkpoint timestamps can appear non-monotonic. Sign in UTC only, and treat the digest chain — not the timestamp — as the authoritative ordering, since a signature over an ISO 8601 string tolerates ntp jitter but the hash chain does not.
- Key rotation mid-batch. Rotating a signer’s Ed25519 key between a
signand a laterverifybreaks verification. Persist a key-id alongsidesigner_idand resolve public keys through a versioned keystore so historical checkpoints remain verifiable for the full record-retention window. - Canonical-form ambiguity. Signing pretty-printed JSON in one stage and compact JSON in another produces different bytes for logically identical payloads. Always hash the exact released bytes and never re-serialize before verification.
- Detached signatures vs. embedded intent. A signature on a bare hash proves integrity but not intent. Including the
stageandsigner_idinside the signed material is what elevates the checkpoint from a checksum to the§ 43.9attestation an auditor will accept.
These checkpoints turn an ingestion graph from a best-effort data flow into a chain of accountable handoffs. When each boundary is signed and verified, the pipeline can prove — to an FAA principal inspector or an EASA reviewer — not only that a maintenance record is intact but that a specific, authorized party released it. For the recordkeeping duties these checkpoints ultimately serve, see the FAA Part-145 Recordkeeping Standards that govern how long each signed artifact must survive and in what form.