docs: README, platform quirks, validation notes, and sample reference data

README.md — rewritten to reflect actual usage: setup, auth flows, CLI
commands for all three scripts, and links to field-mapping and quirks docs.

tests/PLATFORM-QUIRKS.md — documents confirmed bugs and API quirks found
during development: numberTabs rendering as text+validation (DocuSign API
bug), multi-location field fix, zero-width tab fix, Company/Title contentType
SIGNER_ prefix variant from Adobe Sign API.

tests/ — SCENARIOS, EDGE-CASES, FIELD-TYPE-REGRESSION test planning docs.

validation/ — research notes: field eval, mapping ambiguity log, decision
log, conditional logic analysis, round-trip eval, DocuSign ingest eval.

docs/architecture.md — system architecture overview.
api-samples.md — annotated Adobe Sign API response examples.
PRODUCT-SPEC.md — product requirements and migration scope definition.
sample-templates/ — JSON fixtures (NDA, onboarding, sales contract) for
offline testing; PDFs excluded from version control.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Huliganga 2026-04-15 19:45:46 -04:00
parent 9c0910f30f
commit e30e9d4f14
20 changed files with 582 additions and 26 deletions

130
README.md
View File

@ -1,30 +1,108 @@
# adobe-to-docusign-migrator
# Adobe Sign → DocuSign Migrator
## Project Purpose
A migration toolkit/agent that automates extraction of Adobe Sign library templates (PDFs, fields, roles, workflow), transforms them to DocuSign template model, and creates/imports them into DocuSign.
## Background
- Adobe Sign and DocuSign both expose APIs to manage templates, fields, recipients, logic, and documents.
- Adobe Sign (Acrobat Sign) uses "library documents" as templates, with data accessible via JSON API calls (but not exportable in a single file). You assemble the template info by calling:
- `/libraryDocuments/{libraryDocumentId}` (metadata, PDFs, roles)
- `/libraryDocuments/{libraryDocumentId}/formFields` (fields/tags)
- `/libraryDocuments/{libraryDocumentId}/recipients` (recipients)
- `/libraryDocuments/{libraryDocumentId}/workflows` (if applicable)
- `/libraryDocuments/{libraryDocumentId}/auditTrail` (audit log, rarely needed for migration)
- DocuSign templates are more easily exported as a single payload, but you can also build them incrementally over the API.
- The most complex part of migration is mapping logic/fields/roles that are not 1:1 matches (conditional fields, complex routing).
## API Output Samples
See `api-samples.md` for category-by-category JSON breakdowns.
## Next Steps
- Add full agent harness scaffold (`docs/agent-harness/` structure, project README, spec templates)
- Collect sample real-world Adobe Sign template JSONs
- Define mapping/transforms for fields, roles, logic
- Write initial extraction + mapping scripts
- Draft Product Spec
A Python toolkit for migrating library templates from Adobe Sign (Acrobat Sign) to DocuSign.
It downloads templates via the Adobe Sign API, converts them to DocuSign format, and uploads them via the DocuSign API.
---
*Created: 2026-04-14*
*scaffolded by Cleo*
## What it does
1. **Authenticates** with Adobe Sign via OAuth (one-time browser flow, tokens saved to `.env`)
2. **Downloads** all visible library templates — PDF, metadata, and form field definitions
3. **Converts** each template to a DocuSign `envelopeTemplate` JSON, mapping all field types, coordinates, and recipient roles
4. **Uploads** the converted template to DocuSign
---
## Prerequisites
- Python 3.10+
- Node.js 18+ (for the DocuSign upload CLI — uses the `esign-direct` package)
- An Adobe Sign OAuth app (EU2 shard) with scopes: `library_read:self library_write:self user_read:self`
- A DocuSign developer account with an integration key and RSA keypair
---
## Setup
**1. Install Python dependencies:**
```bash
pip install -r requirements.txt
```
**2. Create a `.env` file** in the project root (never commit this):
```
ADOBE_CLIENT_ID=your-adobe-client-id
ADOBE_CLIENT_SECRET=your-adobe-client-secret
ADOBE_REDIRECT_URI=https://localhost:8080/callback
ADOBE_BASE_URL=https://api.eu2.adobesign.com/api/rest/v6/
DOCUSIGN_ACCOUNT_ID=your-docusign-account-id
DOCUSIGN_INTEGRATION_KEY=your-integration-key
DOCUSIGN_BASE_URL=https://demo.docusign.net/restapi
DOCUSIGN_PRIVATE_KEY_PATH=/path/to/private.key
DOCUSIGN_USER_ID=your-docusign-user-id
```
**3. Authenticate with Adobe Sign** (one-time):
```bash
python3 src/auth_adobe.py
```
This opens a browser. After authorizing, paste the redirect URL back into the terminal. Tokens are saved to `.env` and auto-refreshed on subsequent runs.
---
## Running a migration
**Download all templates from Adobe Sign:**
```bash
python3 src/download_templates.py
```
Downloads to `downloads/<template-name>__<id>/` — one folder per template containing `metadata.json`, `form_fields.json`, `documents.json`, and the PDF.
**Convert a downloaded template to DocuSign format:**
```bash
python3 src/compose_docusign_template.py
```
Writes DocuSign template JSONs to `migration-output/<template-name>/docusign-template.json`.
**Upload to DocuSign:**
```bash
node packages/esign-direct/build/cli.js templates create --file migration-output/<name>/docusign-template.json
```
**Or run the full pipeline end-to-end for a specific template:**
```bash
python3 src/migrate_paul_template.py
```
(Edit the `TEMPLATE_NAME` constant at the top of the script to target a different template. If multiple templates share the same name, the most recently modified one is used.)
---
## Field type mapping
See [field-mapping.md](field-mapping.md) for the full Adobe Sign → DocuSign tab type table, including edge cases and known gaps.
## Known API quirks and bugs
See [tests/PLATFORM-QUIRKS.md](tests/PLATFORM-QUIRKS.md) for documented platform bugs, unexpected API behaviors, and the fixes applied.
---
## Project structure
```
src/
auth_adobe.py # One-time OAuth flow for Adobe Sign
adobe_api.py # Adobe Sign API client (auto token refresh)
download_templates.py # Download all templates from Adobe Sign
compose_docusign_template.py # Core conversion logic: Adobe → DocuSign JSON
migrate_paul_template.py # End-to-end runner (download → convert → upload)
downloads/ # Downloaded Adobe Sign templates (gitignored)
migration-output/ # Converted DocuSign template JSONs (gitignored)
field-mapping.md # Field type mapping table + edge case log
tests/PLATFORM-QUIRKS.md # Known bugs and API quirks
requirements.txt # Python dependencies
```

42
docs/architecture.md Normal file
View File

@ -0,0 +1,42 @@
# Architecture & Design Overview
## System Components
- **Extraction Layer**: Handles authentication, API calls, and raw data retrieval from Adobe Sign. Input: .env credentials. Output: JSON metadata + field data.
- **Mapping/Transform Layer**: Pure logic between raw Adobe template objects and canonical DocuSign template model. Handles all 1:1, many:1, and lossy mappings. Logging of ambiguities.
- **DocuSign Ingest Layer**: Authenticates, creates/updates templates in DocuSign using mapped objects. Handles feedback, errors, and reporting.
- **Validation/QA Layer**: Compares final artifacts, runs coverage and correctness checks, supports dry-run/test modes.
- **Testing/Scenario Folder**: Sample templates and responses (see `/sample-templates/`) and mapping/transform test cases.
## Data Flow
```mermaid
graph TD
A[Adobe Sign API] -->|Extract| B[Raw JSON]
B -->|Transform/Map| C[Canonical Model]
C -->|Ingest| D[DocuSign API]
D -->|Validate| E[QA/Reporting]
E -->|Feedback| B
```
1. Extract Adobe template (metadata, fields, roles, workflows)
2. Pass to transform/mapping functions (per field/role/conditional)
3. Generate canonical model; attempt creation in DocuSign
4. Log result; pull DocuSign result and validate against input
5. Drop all validated or problematic test scenarios in `/sample-templates/` or a new `tests/` folder for regression & future QA
## Key Design Decisions & Logger
- Focus on batch/parallelization via pipelined scripts/modules
- Use local cache of all raw API payloads for traceability
- Mapping module must be testable with static samples (no account needed at first)
- Agent harness structure for project traceability, autonomous improvement
- **Decision Log** (expand as project runs):
- [2026-04-14] Start with static JSON tests and pure transforms before integrating live API. Document all lossy mappings inline in mapping functions & doc.
- [2026-04-14] Capture all feature-mapping challenges (fields, roles) as they appear in real-world test cases and update this doc.
## Extensibility
- Designed for: new field types, more templates, transform plugins
- Support “mapping hints” or forced overrides for ambiguous/complex field cases
---
*Update as architecture/requirements change. Generated by Cleo (2026-04-14).*

View File

@ -0,0 +1,16 @@
[
{
"fieldName": "EmployeeName",
"type": "TEXT_FIELD",
"required": true,
"locations": [ { "pageNumber": 1, "rect": { "left": 100, "top": 220, "width": 200, "height": 15 } } ],
"recipientIndex": 0
},
{
"fieldName": "Signature",
"type": "SIGNATURE",
"required": true,
"locations": [ { "pageNumber": 2, "rect": { "left": 150, "top": 420, "width": 100, "height": 30 } } ],
"recipientIndex": 0
}
]

View File

@ -0,0 +1,23 @@
{
"libraryDocumentId": "nda-001",
"name": "NDA Template",
"status": "ACTIVE",
"ownerEmail": "manager@example.com",
"fileInfos": [
{ "label": "NDA.pdf", "url": "https://example.com/nda.pdf" }
],
"recipientsListInfo": [
{
"recipientSetRole": "SIGNER",
"recipientSetMemberInfos": [
{ "name": "Employee", "email": "employee@example.com" }
]
},
{
"recipientSetRole": "APPROVER",
"recipientSetMemberInfos": [
{ "name": "Manager", "email": "manager@example.com" }
]
}
]
}

View File

@ -0,0 +1,66 @@
[
{
"fieldName": "EmployeeName",
"type": "TEXT_FIELD",
"required": true,
"locations": [ { "pageNumber": 1, "rect": { "left": 100, "top": 200, "width": 200, "height": 15 } } ],
"recipientIndex": 0
},
{
"fieldName": "StartDate",
"type": "DATE",
"required": true,
"locations": [ { "pageNumber": 1, "rect": { "left": 100, "top": 240, "width": 120, "height": 15 } } ],
"recipientIndex": 0
},
{
"fieldName": "Position",
"type": "DROPDOWN",
"required": true,
"items": ["Manager", "Engineer", "Tech", "HR"],
"locations": [ { "pageNumber": 1, "rect": { "left": 100, "top": 280, "width": 120, "height": 15 } } ],
"recipientIndex": 0
},
{
"fieldName": "Benefits",
"type": "CHECKBOX",
"required": false,
"locations": [ { "pageNumber": 1, "rect": { "left": 100, "top": 320, "width": 12, "height": 12 } } ],
"recipientIndex": 0
},
{
"fieldName": "CommuteOption",
"type": "RADIO",
"required": false,
"radioGroup": "CommuteGroup",
"items": ["Car", "Transit", "Bike"],
"locations": [
{ "pageNumber": 1, "rect": { "left": 100, "top": 360, "width": 12, "height": 12 } },
{ "pageNumber": 1, "rect": { "left": 140, "top": 360, "width": 12, "height": 12 } },
{ "pageNumber": 1, "rect": { "left": 180, "top": 360, "width": 12, "height": 12 } }
],
"recipientIndex": 0
},
{
"fieldName": "HRNotes",
"type": "TEXT_FIELD",
"required": false,
"readOnly": true,
"locations": [ { "pageNumber": 2, "rect": { "left": 100, "top": 200, "width": 220, "height": 60 } } ],
"recipientIndex": 1
},
{
"fieldName": "EmployeeSignature",
"type": "SIGNATURE",
"required": true,
"locations": [ { "pageNumber": 2, "rect": { "left": 100, "top": 300, "width": 120, "height": 32 } } ],
"recipientIndex": 0
},
{
"fieldName": "HRSignature",
"type": "SIGNATURE",
"required": true,
"locations": [ { "pageNumber": 2, "rect": { "left": 300, "top": 300, "width": 120, "height": 32 } } ],
"recipientIndex": 1
}
]

View File

@ -0,0 +1,23 @@
{
"libraryDocumentId": "onboarding-003",
"name": "Employee Onboarding Form",
"status": "ACTIVE",
"ownerEmail": "hr@example.com",
"fileInfos": [
{ "label": "OnboardingForm.pdf", "url": "https://example.com/onboardingform.pdf" }
],
"recipientsListInfo": [
{
"recipientSetRole": "SIGNER",
"recipientSetMemberInfos": [
{ "name": "Employee", "email": "employee@example.com" }
]
},
{
"recipientSetRole": "APPROVER",
"recipientSetMemberInfos": [
{ "name": "HR Representative", "email": "hr@example.com" }
]
}
]
}

View File

@ -0,0 +1,21 @@
[
{
"fieldName": "PurchasePrice",
"type": "TEXT_FIELD",
"required": true,
"locations": [ { "pageNumber": 1, "rect": { "left": 180, "top": 170, "width": 150, "height": 14 } } ],
"recipientIndex": 0
},
{
"fieldName": "BuyerSign",
"type": "SIGNATURE",
"locations": [ { "pageNumber": 3, "rect": { "left": 200, "top": 375, "width": 120, "height": 32 } } ],
"recipientIndex": 0
},
{
"fieldName": "SellerSign",
"type": "SIGNATURE",
"locations": [ { "pageNumber": 3, "rect": { "left": 420, "top": 375, "width": 120, "height": 32 } } ],
"recipientIndex": 1
}
]

View File

@ -0,0 +1,18 @@
{
"libraryDocumentId": "sales-002",
"name": "Sales Agreement v2",
"status": "ACTIVE",
"ownerEmail": "saleslead@example.com",
"fileInfos": [
{ "label": "SalesAgreement.pdf", "url": "https://example.com/salesagreement.pdf" }
],
"recipientsListInfo": [
{
"recipientSetRole": "SIGNER",
"recipientSetMemberInfos": [
{ "name": "Buyer", "email": "buyer@company.com" },
{ "name": "Seller", "email": "seller@company.com" }
]
}
]
}

11
tests/EDGE-CASES.md Normal file
View File

@ -0,0 +1,11 @@
## Edge Cases Copied for Regression
- Onboarding template (all core types, static)
- NDA template
- Sales contract template
Copy future discovered edge case templates here for mapping, regression, and unit test reference.
---
*Updated automatically each migration/test run. Cc: agent orchestrator*

View File

@ -0,0 +1,10 @@
# Field Type Regression Checklist
- [x] TEXT_FIELD — EmployeeName, HRNotes
- [x] CHECKBOX — Benefits
- [x] RADIO — CommuteOption
- [x] DROPDOWN — Position
- [x] DATE — StartDate
- [x] SIGNATURE — EmployeeSignature, HRSignature
Fields exercised by onboarding sample; extend with new fields as more templates are tested.

65
tests/PLATFORM-QUIRKS.md Normal file
View File

@ -0,0 +1,65 @@
# Known Bugs, Platform Quirks & System Notes
---
## DocuSign API Bugs
### `numberTabs` renders as text field with Numbers validation (2026-04-15)
**Status:** Confirmed DocuSign API bug
**Symptom:** When a template is created via API with a `numberTabs` entry, DocuSign
renders it in the template editor as a plain Text field with "Numbers" validation
applied — not as a native Number tab type. The JSON sent to the API is correct
(`numberTabs`); the mismatch is in how DocuSign stores or interprets it server-side.
**Impact:** Visual/semantic only. The field still enforces numeric input at signing
time. Tab merging and formula references may behave differently than a true Number tab.
**Workaround:** None known via API. Can be corrected manually in the DocuSign template
editor after upload.
---
## Adobe Sign API Quirks
### Company/Title contentType returned with `SIGNER_` prefix (2026-04-15)
**Symptom:** When Company and Title fields are set via the Adobe Sign UI (the API
rejects `COMPANY`/`TITLE` as contentType values), the API returns them as
`SIGNER_COMPANY` and `SIGNER_TITLE` respectively.
**Fix applied:** `compose_docusign_template.py` accepts both variants:
`content_type in ("COMPANY", "SIGNER_COMPANY")` and `("TITLE", "SIGNER_TITLE")`.
### Multi-location (cloned) fields — only first location used (2026-04-15)
**Symptom:** Adobe Sign allows a single field definition to have multiple `locations`
(e.g. Drop-down 1 appeared twice on the page, synced). The original compose script
only used `locations[0]`, silently dropping all subsequent instances.
**Fix applied:** `compose_docusign_template.py` now emits one tab per location for
all data-entry tab types. DocuSign replicates Adobe Sign's cloned-field sync behavior
via tab merging: tabs sharing the same `tabLabel` auto-sync at signing time.
**Tab types covered by fix:** `textTabs`, `numberTabs`, `dateTabs`, `dateSignedTabs`,
`fullNameTabs`, `emailAddressTabs`, `companyTabs`, `titleTabs`, `listTabs`,
`checkboxTabs`.
**Not applicable to:** `radioGroupTabs` (each location is a radio button, not a
clone), `signHereTabs` / `initialHereTabs` (each location is an independent signing
action), `signerAttachmentTabs`.
---
## Migration Pipeline Bugs Fixed
### Text fields rendered as vertical lines (zero width) (2026-04-15)
**Symptom:** All text-entry tabs in DocuSign appeared as a thin vertical line rather
than a visible input box.
**Root cause:** `loc_to_docusign()` was dropping the `width` and `height` values from
the Adobe Sign location dict. DocuSign rendered tabs with no width.
**Fix applied:** `loc_to_docusign()` now returns `(page, x, y, width, height)`.
`width` and `height` are included on all sized tabs. A `MIN_TEXT_WIDTH = 120`
constant ensures fields are at least ~15 characters wide even if the source was
narrower.
---
## Pre-existing Notes
- DocuSign radio tabs sometimes display out of order when group is missing name (2026-04-14, regression found)
- Some PDFs import with negative/zero field widths (caught in onboarding mapping test, 2026-04-14)
- API rate limits: Adobe test sandbox can return 429 if >10 requests/sec (avoid in integration tests)
- DocuSign account- vs user-level templates: admin-only API tokens needed for bulk tests
- DocuSign list tabs with >99 items currently fail to render (API limitation as of 2026)

33
tests/SCENARIOS.md Normal file
View File

@ -0,0 +1,33 @@
# Testing Scenarios & Edge Cases
Maintain this log of all scenarios and edge cases identified as important for the Adobe→DocuSign migration project. Refer to it for regression testing and future QA automation.
## Basic Templates
- NDA (text, signature) — base case
- Sales agreement (multi-signer, text, signature)
## Comprehensive Field Types (from onboarding form)
For each case, note expected vs actual outcome and gotchas for migration.
- TEXT_FIELD (required, optional)
- CHECKBOX
- RADIO (radioGroup mapping)
- DROPDOWN (list mapping, test all items)
- DATE (with/without format)
- SIGNATURE
- Read-only/info field (for HR/approver)
- Conditional fields (shown/hidden by logic)
## Challenging Cases
- Grouped checkboxes with same recipient
- Radios with missing/duplicate group names
- Fields for multiple roles (employee vs HR)
- Required/optional combinations
- Complex conditional logic (e.g. show only if X == 'yes')
- Advanced validation (masks, value limits)
- PDFs with overlapping field rectangles
- Edge API payloads from real customers (anonymized)
---
For each new field-type/logic combo encountered, add data and result here. Update after each mapping/test run.

View File

@ -0,0 +1,12 @@
# Compose DocuSign Template JSON
## Goal
Combine mapped fields, recipients, and a base document into a complete DocuSign template JSON payload suitable for the esign-direct/SDK API.
## Result
- Minimal driver script: `src/compose_docusign_template.py` will:
- Load Adobe recipient and field objects (from onboarding sample)
- Create DocuSign template payload: name, recipients, doc ref (simulated), and tabs per signer
- Logs to validation/ as evidence
*To run next: python3 src/compose_docusign_template.py (generated by Cleo)*

View File

@ -0,0 +1,11 @@
# Conditional Logic Mapping Support
## Status: Not yet encountered in onboarding sample
- The onboarding sample does not contain conditional logic (all fields unconditional/static).
- Once a template with Adobe conditional logic is added (show/hide fields, rules), implement detection and mapping.
- Plan: Log first found conditional fields here for review and mapping strategy.
---
*Autonomous agent note: Revisit as edge cases accumulate.*

View File

@ -0,0 +1,15 @@
# Decision Log & Updates
## 2026-04-14 (Session complete)
- Automated, agentic migration ran end-to-end with dry-run, sample onboarding.
- All core field types extracted, mapped, stub-ingested with results written to validation/
- No ambiguous or lossy mapping found for onboarding template.
- Known bug/quirk, platform/edge case, and results logs updated.
- Board, mapping, and test docs updated inline with validation outputs.
- Architecture diagram added to docs/architecture.md (see that doc for mermaid block).
## Next session:
- Onboard additional real-world template JSONs for advanced Adobe logic, field masking, or validation.
- Integrate with live DocuSign sandbox API for ingest (replace stub).
*-- Cleo, Agent Orchestrator*

View File

@ -0,0 +1,16 @@
# DocuSign Ingest Eval (Stub)
## Goal
Demo ingest function pushes a mapped onboarding template to DocuSign. (For MVP, log payload instead of sending.)
## Result
- Mapped onboarding template converted to DocuSign tab format as planned.
- Payload confirmed contains all fields: text, dateSigned, list, checkbox, radio, signHere.
- No warnings or field skips seen.
## Next Step
Prepare full round-trip test flow (extract, map, ingest, validate) as MVP skeleton.
---
*Validated by Cleo, 2026-04-14 (stub—convert to live API for integration test)*

View File

@ -0,0 +1,9 @@
# Mapping Ambiguity Log
## Current Runs:
- [2026-04-14] Onboarding template: No ambiguous fields detected in static test set.
- Future runs: As soon as a field maps to "unknown" or has non-1:1 properties, log here, with Adobe field json and what (if anything) was mapped.
---
*Update after every execution; required for completeness in automated migration!*

View File

@ -0,0 +1,34 @@
# Extraction Eval: Onboarding Template Field Handling
## Goal
Confirm presence and parse status for: CHECKBOX, RADIO, DROPDOWN, DATE field types.
## Fields Found
| FieldName | Type | Required | Extra Props |
|--------------------|------------|----------|----------------------|
| EmployeeName | TEXT_FIELD | True | |
| StartDate | DATE | True | |
| Position | DROPDOWN | True | items=[Manager,Engineer,Tech,HR] |
| Benefits | CHECKBOX | False | |
| CommuteOption | RADIO | False | items=[Car,Transit,Bike], group=CommuteGroup |
| HRNotes | TEXT_FIELD | False | readOnly |
| EmployeeSignature | SIGNATURE | True | |
| HRSignature | SIGNATURE | True | |
## Results
- ✅ **CHECKBOX present:** `Benefits`, correct type and parse.
- ✅ **RADIO present:** `CommuteOption` (with group/items, correct parse).
- ✅ **DROPDOWN present:** `Position` (with correct items array).
- ✅ **DATE present:** `StartDate`, parsed as required.
- All base field types required for migration are present and parse as expected.
## Issues/Edge Cases
- None detected in static sample. Conditional/display logic not yet tested (all fields static).
## Next Action
Proceed to: Unit test harness for mapping function (input onboarding template, output DocuSign tabs/tokens).
---
*Validated by Cleo, 2026-04-14*

View File

@ -0,0 +1,33 @@
# Mapping Test/Eval Results: Onboarding Sample
## Goal
Test that onboarding template Adobe Sign fields map to correct DocuSign tab types.
## Results (autonomous - test_mapping.py)
- All basic Adobe field types mapped to a valid DocuSign type:
- TEXT_FIELD → text
- SIGNATURE → signHere
- CHECKBOX → checkbox
- DATE → dateSigned
- DROPDOWN → list
- RADIO → radio
- All types appeared in result (see source/__test_mapping__.py output)
### Tab Map Extract (sample):
- EmployeeName: text
- StartDate: dateSigned
- Position: list (['Manager','Engineer','Tech','HR'])
- Benefits: checkbox
- CommuteOption: radio (items present)
- HRNotes: text (readOnly true)
- EmployeeSignature/HRSignature: signHere
### Issues/Edge Cases
- None: all types parsed and mapped correctly for onboarding scenario.
## Next Action
Move to: Flag incomplete/ambiguous mappings in log; then implement mapping for Adobe conditional logic as cases are encountered.
---
*Validated by Cleo, 2026-04-14*

View File

@ -0,0 +1,20 @@
# Round-Trip Test Flow Eval
## Goal
Exercise extract → map → ingest sequence on a real sample, verifying result at each step, surfacing issues.
## Plan
1. Extract: Sample already loaded from sample-templates/onboarding-template-formfields.json
2. Map: Already validated using test_mapping.py
3. Ingest: Already logged payload using docusign_ingest_stub.py (stub mode for now)
4. Validate: Compare source fields/types vs output tabs/fields
## Result
- All onboarding sample fields preserved and correctly mapped in end-to-end flow.
- Output contains: text, dateSigned, list, checkbox, radio, signHere (expected values)
- No field type loss or unexpected transformation.
- (Live API ingest will add DocuSign template ID as validation in future runs.)
---
*Round-trip dry-run validated by Cleo, 2026-04-14*