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 Python toolkit for migrating library templates from Adobe Sign (Acrobat Sign) to DocuSign.
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. It downloads templates via the Adobe Sign API, converts them to DocuSign format, and uploads them via the DocuSign API.
## 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
--- ---
*Created: 2026-04-14* ## What it does
*scaffolded by Cleo*
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*