pub struct TransferRecord {
pub transfer_id: Uuid,
pub passport_id: PassportId,
pub from_operator: ResponsibleOperator,
pub to_operator: ResponsibleOperator,
pub reason: TransferReason,
pub from_signature: Option<String>,
pub node_acceptance_attestation: Option<String>,
pub initiated_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub rejected_at: Option<DateTime<Utc>>,
pub cancelled_at: Option<DateTime<Utc>>,
pub notes: Option<String>,
}Expand description
A single transfer-of-responsibility event in the DPP lifecycle.
Recorded in two steps — the outgoing operator authorises the handover, and
the hosting node then attests that the incoming operator accepted it. The
two are not symmetric: see TransferRecord::from_signature and
TransferRecord::node_acceptance_attestation, only the first of which is
a party’s own signature.
The chain this builds is an auditable local record of what the node was told. It is not proof of who holds the obligations: under Implementing Regulation (EU) 2026/1778 Art. 6a that is the EU registry’s record, between actors whose identity is verified by eIDAS means under its Arts. 4-5.
Fields§
§transfer_id: UuidUnique identifier for this transfer event.
passport_id: PassportIdThe passport being transferred.
from_operator: ResponsibleOperatorThe outgoing (previous) responsible operator.
to_operator: ResponsibleOperatorThe incoming (new) responsible operator.
reason: TransferReasonThe reason for this transfer.
from_signature: Option<String>Compact JWS signature from the outgoing operator, signing over the transfer payload to authorise the handover.
Produced by the node that hosts the outgoing operator, using that operator’s key — so in the managed single-node model this genuinely is the outgoing party’s signature. Its counterpart below is not.
node_acceptance_attestation: Option<String>This node’s attestation that the incoming operator accepted the handover. Not a signature by the incoming operator.
The incoming operator is a party with no key on this node, so nothing
here can be signed by them. What is recorded is a JWS over the same
Self::signing_payload, produced by the same key that produced
Self::from_signature, at the moment the acceptance step ran. Because
the payload is RFC 8785 canonical, the JWS header carries no nonce or
timestamp, and Ed25519 is deterministic, the two values are byte-
identical in the managed model. The second one therefore carries
exactly one bit — that the acceptance step ran — and completed_at
carries the same bit with a timestamp attached.
It is named for what it is because the previous name, to_signature,
was not. That name invited every consumer — including the registry
notification built from this record — to read the value as the incoming
operator’s own authorisation, which is a claim no node can make about a
counterparty whose key it does not hold.
The authoritative record of who holds the obligations is the EU
registry, under Implementing Regulation (EU) 2026/1778 Art. 6a, between
actors whose identity is verified by eIDAS means under its Arts. 4–5.
A chain of did:web identifiers is not evidence of that standing. See
docs/regulatory/COMPLIANCE.md § Transfer-of-Responsibility Article Pin.
alias keeps chains stored under the old key readable: without it every
already-completed transfer would deserialize with this field None and
Self::is_complete would start answering false for handovers that
did complete.
initiated_at: DateTime<Utc>Timestamp when the transfer was initiated.
completed_at: Option<DateTime<Utc>>Timestamp when the transfer was completed — the outgoing operator had
authorised it and the acceptance step had run.
None if the transfer is still pending acceptance.
rejected_at: Option<DateTime<Utc>>Timestamp when the incoming operator explicitly rejected the transfer.
Set by TransferRecord::reject; makes the record terminal.
cancelled_at: Option<DateTime<Utc>>Timestamp when the outgoing operator cancelled the transfer.
Set by TransferRecord::cancel; makes the record terminal.
notes: Option<String>Free-text notes (e.g. conditions, regulatory references).
Implementations§
Source§impl TransferRecord
impl TransferRecord
Sourcepub fn signing_payload(&self) -> Value
pub fn signing_payload(&self) -> Value
The canonical content signed over at both steps: the immutable core
of the transfer, excluding the signatures themselves and the lifecycle
timestamps set after signing (completed_at/rejected_at/cancelled_at).
Self::from_signature and Self::node_acceptance_attestation are
both JWS over the JCS canonicalisation of this value, so both bind the
same immutable handover terms and tampering any bound field invalidates
both.
Note what that implies: the same payload signed by the same key yields the same JWS, so in the managed single-node model the two values are byte-identical. That is a property of this design rather than a defect — the second records that the acceptance step ran, and nothing more.
Sourcepub fn status(&self) -> TransferStatus
pub fn status(&self) -> TransferStatus
Determine the current status of this transfer.
Terminal states (Rejected, Cancelled) take priority over signatures,
so a cancelled transfer that already had the from_signature still reports
Cancelled rather than Initiated.
Sourcepub fn is_complete(&self) -> bool
pub fn is_complete(&self) -> bool
Returns true if the handover was authorised, accepted and finalised.
Sourcepub fn reject(&mut self) -> Result<(), TransferError>
pub fn reject(&mut self) -> Result<(), TransferError>
The incoming operator explicitly rejects the transfer.
Only valid from Initiated state. After rejection the record is terminal;
a new transfer may be initiated on the chain.
Sourcepub fn cancel(&mut self) -> Result<(), TransferError>
pub fn cancel(&mut self) -> Result<(), TransferError>
The outgoing operator cancels the transfer before it completes.
Valid from Initiated or Accepted state. After cancellation the
record is terminal; a new transfer may be initiated on the chain.
Sourcepub fn complete(&mut self) -> Result<(), TransferError>
pub fn complete(&mut self) -> Result<(), TransferError>
Mark the transfer as completed once it is authorised and accepted.
Only valid from Accepted state (Self::from_signature and
Self::node_acceptance_attestation both present, no completed_at
yet). This is the final step before the incoming operator becomes the
current responsible operator in this node’s chain — which is a local
record, not the registry’s determination of who holds the obligations.