Theory

The capstone task — signer decline, end to end

Fifteen lessons, one real deployed application. SignFlow went from an empty main to a document-signing workflow with authentication, an audit trail, and a live URL. This capstone asks you to extend it yourself — to prove the patterns are yours, not just read.

The task: signer decline. Right now a signer can sign. Add the ability to decline — to formally refuse to sign, with a reason — and make a declined document a terminal state, exactly the way a completed one is. This is deliberately symmetric with signing: you already have the perfect template one file over. If you can build decline by applying what sign taught you, you have internalised the track.

What it must do, end to end:

  1. A migration. A signer can be pending, signed, or now declined; a document can be draft, sent, completed, or now declined. Extend the CHECK constraints. Add a decline_reason TEXT to the signer (nullable — only declines have one). (A CHECK constraint can't be altered in place; drop and re-add it in the migration — finding the syntax is part of the exercise.)

  2. A query. DeclineSigner, guarded exactly like SignSigner: UPDATE ... SET status='declined', decline_reason=$2 WHERE id=$1 AND status='pending' (:execrows). Single-use, for the same reason signing is — a spent token declines zero rows.

  3. A handler, in a transaction. On decline: flip the signer to declined, flip the document to declined (one decline ends the whole request — decide and defend that rule), and write an audit event (document.declined, actorSigner) in the same transaction. The document's status transition and the audit row commit together or not at all — the L10–12 pattern exactly.

  4. A template. A Decline button and a reason field on the signing page, next to Sign. A declined signer sees their declined state on return (idempotent, like signed). The owner's roster and audit trail show the decline and its reason.

  5. The constraints honored. The token is still the whole authorization (no document id in the decline request). The audit row is FK-free and survives. The signature caveat still frames what a decline is: evidence that this person refused these exact bytes, nothing more.

Author it from your own verified code — the same discipline the whole track used: write it, compile it, run it, exercise it. When a signer declines, watch the document go declined, the audit trail record it with the reason, and the roster reflect it. That is the capstone: not new concepts, but your hands applying every one of them to a feature that wasn't handed to you.