- Shipped
- August 6, 2026 at 12:54 PM UTC
- Author
- Kamo
- Commit
- 15a969c
envelopeComplete required a literal SIGNED from every recipient, but delegate() leaves the delegator DELEGATED forever and ADDS the replacement instead of substituting it. No delegated envelope could ever reach SIGNED: completedAt was never set, flatten never ran, the signed PDF was never produced, and with no expiry sweeper the only escape was a manual void that loses the artifact. A DELEGATED recipient now counts as discharged when its delegatedToRecipientUid names a replacement on the same roster that has itself discharged — null, dangling and looping pointers still leave the envelope incomplete. The signer count is now checked against the template's slot count once, before the first recipient row is written and the first invite email leaves. Over-supply used to throw from inside the per-signer loop, after recipient 1 was saved and emailed: the transaction rolled the row back but could not recall the email, so signer 1 held a live link to an envelope that no longer existed. Under-supply threw nothing at all and flattened with the missing party's required blocks blank. Templates with no declared slots stay exempt in both directions. submit() now shares decoder()'s terminality test. A decoder while another party was pending, followed by that party signing, left the envelope PARTIALLY_SIGNED with nobody able to act on it. Slot binding no longer trusts request order alone: an explicit signerUid still wins, then the slot pinned to the signer's memberId, then the slot pinned to their email, and only then position. Staff never see the slots, so position bound a two-party template to whoever was typed first.