Step 5 — signature is a modular step

sent_for_signature__pending is one state in the graph, and the thing that fires it is a small trait — not a vendor. DocuSign is the shipped implementation; dev and tests run a recording stub, so the step stays testable without an account.

From web/src/signature.rs:

pub trait SignatureProvider: Send + Sync {
    /// Submit the rendered retainer PDF for the given notation, placing
    /// the fields described by `manifest`. Returns a provider-issued id
    /// correlating future events.
    async fn send_for_signature(
        &self,
        notation_id: Uuid,
        pdf: &[u8],
        manifest: &SignatureManifest,
    ) -> Result<SignatureRequestId, SignatureError>;

Because the step is modular it can also be careful. Dispatch is idempotent — a notation that already has an envelope out reuses it, fires nothing, and sends nothing — so a retry can never double-send a client's contract.

From web/src/retainer_walk.rs:

    // Idempotency: this notation already has an envelope out. Reuse the
    // persisted id, fire nothing, send nothing — the post-state is
    // whatever the notation already records.
    if let Some(existing) = notation_row.signature_request_id.clone() {
        return Ok((
            StateName::from(notation_row.state.as_str()),
            crate::signature::SignatureRequestId(existing),
        ));
    }