Skip to content

feat(node): locked prefill and multi-document signing for template envelopes - #91

Merged
esaugomez31 merged 8 commits into
mainfrom
feat/direct-envelope-signing
Sep 11, 2026
Merged

esaugomez31 merged 8 commits into
mainfrom
feat/direct-envelope-signing

Conversation

@esaugomez31

Copy link
Copy Markdown
Contributor

Why

Signing from a template envelope puts the signer directly on the document, with no form in front of it. The package could already create that envelope, but not with the values the host computed: the document opened empty and editable. The signer role was also fixed to signer, so templates that name their role differently could not be used, and an agreement made of several documents needed one envelope per document.

What

  • createEnvelope takes an optional prefill written onto the template's text tabs, and each value can be locked. It is validated before any request, like the Web Forms prefill.
  • DOCUSIGN_SIGNER_ROLE sets the template role the signer fills (default signer).
  • DOCUSIGN_TEMPLATE_ID accepts several comma-separated ids, sent as one envelope of composite templates: one signer and one signing session, with the documents in the order given. A single id sends the same request as before.

Notes

  • The envelope service (createEnvelopeService) does not forward a prefill yet, so for now a host calls the provider directly.
  • Verified against the DocuSign developer environment.

@esaugomez31 esaugomez31 self-assigned this Sep 11, 2026
@esaugomez31
esaugomez31 marked this pull request as ready for review September 11, 2026 16:57
@esaugomez31
esaugomez31 requested a review from grimen September 11, 2026 17:14
@grimen

grimen commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@esaugomez31 I reviewed this and pushed seven commits on top of yours that address what came up, could you look them over? Nothing you verified live should behave differently; the two deliberate changes only matter in cases the live run did not touch.

What the commits do

  1. fix(node): one prefill parser for Web Forms and envelopes. parseEnvelopePrefill / assertEnvelopePrefill reuse the parser parseWebFormPrefill had, so the envelope path gets the field-name rule, the 200-field cap, a per-field reason and the __proto__ defence. A locked empty value is refused (neither side could ever fill it). The error class is PrefillError; WebFormPrefillError stays as an alias.
  2. fix(node): a bare value (or { value } without locked) no longer sends locked: 'false'. A request property overlays the template tab's, so that silently unlocked tabs the template designer had locked. locked now travels only when the host set it.
  3. fix(node): the template list is normalised once in docuSignConfigFromEnv (templateIds(config) reads it), so DOCUSIGN_TEMPLATE_ID=' , ' fails at service boot like an unset one. The provider checks the setting before withRetry: a missing template is reported once as VALIDATION_ERROR instead of three retries into PROVIDER_UNAVAILABLE.
  4. fix(node): prefill now reaches the provider through CreateEnvelopeInput / createEnvelopeService, the service's tracing wrapper (which was silently dropping the 4th argument) and the mock (getEnvelopePrefill). It stays off the GraphQL mutation on purpose: terms the signer cannot change are the host's to supply, not the client's, the same reason the mint resolves them from TERMS_URL. Wiring TERMS_URL for envelopes in esign-service is a follow-up.
  5. docs(node): the SIGNER_RECIPIENT_ID comment now states DocuSign's actual merge rule (email + name + routing order, role matched by name), and the docs say the role must sit at the same routing order in every listed template.
  6. fix(service): the fixture template gains the Text tabs reference and notes so make docusign-template produces something the prefill can write to (the definition moved to src/providers/docusign/template.ts, unit-tested); WRITE=1 leaves a comma-separated DOCUSIGN_TEMPLATE_ID alone instead of collapsing it to one id. Note the script reuses the fixture by name, so the new tabs only reach a sandbox after deleting the old fixture template there.
  7. docs: DOCUSIGN_SIGNER_ROLE everywhere DOCUSIGN_TEMPLATE_ID is listed, the proxy runbook no longer says the role must be signer, the Text-tab-only rule (a label naming a Number/Date/List tab is ignored with a 200 and a blank editable field), the ESignProvider transcription and API contract, and a new docs/integration/locked-terms-envelopes.md recipe.

Gates locally: esign-node 473 tests, esign-service 395 tests, both at 100% coverage; typecheck, lint, make docs-check, commitlint all green.

One ask on the PR body: the "does not forward a prefill yet" note in Notes can become "the service and wrapper forward it for embedding hosts; the GraphQL mutation has no prefill input by design; esign-service resolving it from TERMS_URL is a follow-up".

If you can re-run your live check against the sandbox with a locked value and, if you have two templates handy, a comma-separated list, that would confirm the lock and the single-session behaviour end to end.

esaugomez31 and others added 8 commits September 11, 2026 23:07
parseEnvelopePrefill / assertEnvelopePrefill share the parser
parseWebFormPrefill uses: the field-name rule, the 200-field cap, the
per-field reason and the prototype defence now apply to envelope tabs
too, and a locked tab with nothing in it is refused. The provider maps
PrefillError (WebFormPrefillError kept as an alias) to VALIDATION_ERROR
on both the envelope and the hosted-form path through one helper.
A property in the request overlays the template tab's, so sending an
explicit unlock for every value the host had not marked as locked
unlocked tabs the template designer had made read-only. The lock now
travels only when the host set it, as 'true' or 'false'.
docuSignConfigFromEnv stores DOCUSIGN_TEMPLATE_ID trimmed with blank
entries dropped, templateIds(config) reads the list, and
missingDocuSignConfig counts a list naming no template as unset. The
service boot guard, assertDocuSignConfig and the client now agree, so
a lone comma fails at boot instead of on the first envelope. The
provider checks the setting before any request and reports it as the
caller's VALIDATION_ERROR once, no longer as PROVIDER_UNAVAILABLE after
three retries.
…wrapper and the mock

CreateEnvelopeInput carries prefill and createEnvelopeService forwards
it; the service's tracing wrapper forwards it (never onto a span) and
the mock keeps it per envelope (getEnvelopePrefill), so a host's test
can read back what reached the provider. The GraphQL mutation stays
without it: terms the signer cannot change are the host's to supply,
not the client's.
Composite templates discard inline recipient ids; DocuSign merges the
signer's copies by email, name and routing order and matches them to
the server template's role by name. One signing session therefore
depends on the configured role sitting at the same routing order in
every listed template, which the comment now says.
…s a template list

The proxy-flow fixture (now src/providers/docusign/template.ts, the
script a thin runner over it) adds the Text tabs reference and notes,
so make docusign-template produces a template an envelope prefill can
actually write to. WRITE=1 leaves .env alone when DOCUSIGN_TEMPLATE_ID
already lists several templates instead of collapsing the list to the
fixture's id. Both rules are unit-tested.
…e they are read

DOCUSIGN_SIGNER_ROLE joins every place DOCUSIGN_TEMPLATE_ID is listed
(the development guide, the production runbook, both .env examples,
the k8s secret); the proxy runbook no longer says the role must be
named signer, states the Text-tab rule and the same-routing-order rule
for template lists, and knows the fixture's new tabs; the ESignProvider
transcription and the API contract carry the prefill parameter and why
the mutation has none; docusign-lessons gains the envelope-side rules;
and locked-terms-envelopes.md is the host-side recipe, listed in the
index and linked from its Web Forms sibling.
@grimen
grimen force-pushed the feat/direct-envelope-signing branch from 01cf839 to d90b972 Compare September 11, 2026 21:09
@esaugomez31

Copy link
Copy Markdown
Contributor Author

.
.
.

If you can re-run your live check against the sandbox with a locked value and, if you have two templates handy, a comma-separated list, that would confirm the lock and the single-session behaviour end to end.

Thanks @grimen I just tested it on the emulator and it works well.

@esaugomez31
esaugomez31 merged commit 82de337 into main Sep 11, 2026
20 checks passed
@esaugomez31
esaugomez31 deleted the feat/direct-envelope-signing branch September 11, 2026 22:12
@github-actions github-actions Bot mentioned this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants