Add RetryableHTTPError and raise_for_retryable_status() to retry.py, then
wire up @retry_with_backoff across all network-touching functions:
- All 5 public Adobe Sign API functions in adobe_api.py
- upload_template() and find_existing_template() in upload_docusign_template.py
raise_for_retryable_status() distinguishes transient errors (429, 500, 502,
503, 504) from auth/client errors — only transient errors are retried.
Auth refresh functions are intentionally left undecorated since a 401 there
means bad credentials, not a transient failure.
Backoff: 1s → 2s → 4s, max 16s, max 3 retries (131 tests passing).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix ValueError crash in migrate_template.py and migrate_paul_template.py:
compose_template() returns a 3-tuple since Phase 23 but both CLI scripts
were still unpacking 2 values
- Fix ImportError in bulk-send/bulk_send.py: replace non-existent auth_helper
import with docusign_auth.get_access_token via sys.path
- Activate log sanitizer at web app startup so tokens never appear in logs
- Log a warning at startup when SESSION_SECRET_KEY is the default dev value
- Add reportlab to requirements.txt (used by generate_pdfs.py, was missing)
- Move asyncio import from bottom of templates.py to top where it belongs
- Correct stale coordinate comment in generate_pdfs.py (both platforms use
top-left origin; the comment incorrectly described bottom-left inversion)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces flat warning strings with machine-readable FieldIssue objects
(code, field_name, message, severity) emitted during compose and surfaced
in all migration result paths via a new field_issues[] key.
Codes: CROSS_RECIPIENT_CONDITIONAL, UNSUPPORTED_OPERATOR, HIDE_ACTION,
MULTI_PREDICATE, INVALID_PARENT_TAB, FIELD_TYPE_SKIPPED, PARTIAL_FIELD_TYPE
Cross-recipient conditional detection: compose now builds a field→assignee
map and flags conditions where the trigger field belongs to a different
recipient — the main cause of the CONDITIONALTAB_HAS_INVALID_PARENT 400.
UI changes:
- Success rows with field_issues show ⚠️ icon + amber "partial" badge
- Results, History & Audit, and Template Detail history tab all show
field issues grouped by code in collapsible sections within expanded rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DocuSign returns CONDITIONALTAB_HAS_INVALID_PARENT when a conditional tab
references a parent that doesn't exist or is a forbidden type (signature,
initial, auto-filled). Added _strip_invalid_conditionals() post-processing
pass that validates all conditionalParentLabel values against the actual
built tabs and removes any that won't pass DocuSign validation, logging a
warning for each. Also updated verify tests for the template role-fetch step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adobe Sign conditionalAction (SHOW/EQUALS) is now translated to
DocuSign's conditionalParentLabel + conditionalParentValue on the
dependent tab, making conditional fields work in the migrated template.
For radio groups, conditionalParentLabel matches the radio group name.
Unsupported cases emit warnings rather than silently dropping conditions:
- HIDE action (no DocuSign equivalent — field left always visible)
- Non-EQUALS operators (skipped)
- Multi-predicate ANY/ALL (first EQUALS predicate used, rest ignored)
Also updates field-mapping.md: adds Conditional Logic Mapping table
and moves this item out of Known Gaps into documented behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adobe STAMP (hanko/seal) has a direct DocuSign equivalent via
stampTabs. Previously marked as skipped with no equivalent.
- compose_docusign_template.py: emit stampTabs for STAMP input type;
PARTICIPATION_STAMP remains skipped (still no equivalent)
- field-mapping.md: update STAMP row, add stampTabs to multi-location
non-merging list, add account feature prerequisite to Known Gaps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adobe Sign uses a non-standard separate endpoint for refresh:
/oauth/v2/refresh (not /oauth/v2/token). Using the wrong endpoint
returned a misleading "Invalid grant_type refresh_token" error.
Also:
- Remove redirect_uri from refresh requests (not required)
- Add clear RuntimeError message directing user to re-authenticate
- Validate access_token is non-empty before saving in adobe_auth.py
- Log token lengths and exchange response keys on successful auth
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches the naming convention of docusign_auth.py. Update all
references in README.md and the error message in adobe_api.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
migrate_template.py — generic end-to-end CLI replacing the earlier
migrate_paul_template.py:
--list list available Adobe Sign templates
--template "Name" download → convert → upload a named template
--template "Name" --skip-upload convert only, write JSON to migration-output/
Picks most recently modified when multiple templates share a name.
create_adobe_template.py — utility for creating a test template in Adobe Sign
that exercises all 15+ field types. Uses the David Tag Demo Form PDF as the
base document and positions extra fields (Number, Email, Company, Title) in
the gaps of the original layout.
generate_pdfs.py — generates realistic sample PDFs with labelled form areas
matching the *-formfields.json fixtures in sample-templates/, for use in
offline testing without a live Adobe Sign account.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docusign_auth.py — authentication helper supporting two flows:
- JWT Grant: service-to-service token generation using an RSA private key;
caches token + expiry in .env to avoid redundant round-trips
- Auth Code Grant (--consent): one-time browser flow to grant the app the
'impersonation' scope required for JWT; must be run once per user/app before
JWT will work
upload_docusign_template.py — posts a docusign-template.json to the DocuSign
Templates REST API (v2.1). No Node.js dependency. Retries once on 401.
requirements.txt — adds PyJWT>=2.0 and cryptography for RSA key handling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
compose_docusign_template.py — converts a downloaded template folder into a
DocuSign envelopeTemplate JSON ready for the Templates API. Key behaviours:
- Full field type mapping: TEXT_FIELD, SIGNATURE, CHECKBOX, RADIO, DROP_DOWN,
BLOCK, FILE_CHOOSER (with warning), INLINE_IMAGE (skipped with warning)
- contentType dispatch: SIGNER_NAME → fullNameTabs, SIGNER_EMAIL →
emailAddressTabs, SIGNATURE_DATE → dateSignedTabs, COMPANY/SIGNER_COMPANY →
companyTabs, TITLE/SIGNER_TITLE → titleTabs, DATA+NUMBER → numberTabs,
DATA+DATE → dateTabs, SIGNER_INITIALS → initialHereTabs
- Multi-location (cloned) fields: emits one tab per location with the same
tabLabel so DocuSign tab merging replicates Adobe Sign's sync behaviour
- Width/height passed through from Adobe Sign locations; MIN_TEXT_WIDTH=120pt
ensures text fields render as visible boxes rather than vertical lines
- Coordinate system: both platforms use top-left origin — no inversion needed
test_mapping.py — unit test harness validating tab grouping and field mapping.
field-mapping.md — full Adobe Sign → DocuSign tab type reference table with
edge cases, known gaps, and decision log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
download_templates.py — subcommand CLI for listing and downloading library
templates from Adobe Sign.
list — print all templates with name, modified date, ID
download — download all templates (default)
download --all — explicit download all
download "Name" — download a single named template; picks the most
recently modified if duplicates exist
Each template is saved to downloads/<name>__<id8>/ containing metadata.json,
form_fields.json, documents.json, and the source PDF.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
auth_adobe.py — one-time browser Auth Code Grant flow; saves access and
refresh tokens to .env. Targets the EU2 shard.
adobe_api.py — thin API client with auto token refresh on 401. Supports
GET, POST (JSON and multipart), PUT, and binary download.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>