---
phase: 11-agent-saved-signature-and-signing-workflow
plan: "02"
type: execute
wave: 2
depends_on:
- "11-01"
files_modified:
- teressa-copeland-homes/src/lib/pdf/prepare-document.ts
- teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts
autonomous: true
requirements:
- AGENT-04
must_haves:
truths:
- "When agent prepares a document with agent-signature fields and a saved signature, the prepared PDF contains the signature image at each agent-sig field coordinate"
- "The agent signature is invisible to the client — it is never returned by GET /api/sign/[token]"
- "When agent prepares a document with agent-signature fields but no saved signature, the prepare route returns 422 with { error: 'agent-signature-missing' } — no silent failure"
- "When agent prepares a document with no agent-signature fields, the prepare route succeeds normally regardless of whether a signature is saved"
artifacts:
- path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
provides: "preparePdf() with agentSignatureData param; embedPng + drawImage at agent-sig field coordinates"
contains: "agentSignatureData"
- path: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts"
provides: "Fetches agentSignatureData from DB; 422 guard when agent-sig fields present but no sig saved; passes to preparePdf()"
contains: "agent-signature-missing"
key_links:
- from: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts"
to: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
via: "preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData)"
pattern: "preparePdf.*agentSignatureData"
- from: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts"
to: "teressa-copeland-homes/src/lib/db/schema.ts"
via: "db.query.users.findFirst({ columns: { agentSignatureData: true } })"
pattern: "agentSignatureData.*findFirst"
---
Wire the agent signature into the prepare pipeline: `preparePdf()` gains an `agentSignatureData` parameter and embeds the PNG at each agent-signature field coordinate using the same `pdfDoc.embedPng()` + `page.drawImage()` pattern already used for client signatures. The prepare route fetches the agent's saved signature from the DB and passes it to `preparePdf()`, with a 422 guard if agent-signature fields are present but no signature has been saved.
Purpose: Fulfills AGENT-04 — agent's saved signature is baked into the prepared PDF before it reaches the client.
Output: Prepared PDFs with embedded agent signatures; 422 error when signature is missing; client signing session unaffected (isClientVisibleField filter already excludes agent-signature).
@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md
@/Users/ccopeland/.claude/get-shit-done/templates/summary.md
@.planning/STATE.md
@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md
From src/lib/pdf/prepare-document.ts (current function signature — BEFORE Phase 11):
```typescript
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record,
sigFields: SignatureFieldData[],
// Phase 11 adds: agentSignatureData: string | null = null
): Promise
```
Current agent-signature stub at line ~138 (to be replaced):
```typescript
} else if (fieldType === 'agent-signature') {
// Skip — agent signature handled by Phase 11; no placeholder drawn here
}
```
From src/lib/signing/embed-signature.ts (confirmed working embedPng pattern):
```typescript
// embedPng accepts base64 DataURL directly (no Buffer conversion needed)
const pngImage = await pdfDoc.embedPng(sig.dataURL); // 'data:image/png;base64,...'
page.drawImage(pngImage, {
x: sig.x,
y: sig.y,
width: sig.width,
height: sig.height,
});
```
From src/app/api/documents/[id]/prepare/route.ts (current POST handler structure):
```typescript
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });
// ... body parsing, doc lookup, path resolution ...
await preparePdf(srcPath, destPath, textFields, sigFields); // 4 args currently
// ... DB update, audit log, return ...
}
```
From src/lib/db/schema.ts (getFieldType — confirmed working pattern):
```typescript
export function getFieldType(field: SignatureFieldData): SignatureFieldType {
return field.type ?? 'client-signature';
}
```
CRITICAL: embedPng is called ONCE per document (not once per field). The returned PDFImage
object is reused across multiple drawImage calls. Call pdfDoc.embedPng(agentSignatureData)
before the field loop, store as agentSigImage, then reference agentSigImage inside the loop.
COORDINATE SYSTEM: Use field.x, field.y, field.width, field.height directly. These are already
stored in PDF user-space (bottom-left origin) by FieldPlacer's screenToPdfCoords() conversion.
Do NOT invert or adjust Y — this is identical to how embedSignatureInPdf() works for client sigs.
IMPORT NOTE: PDFImage type from @cantoo/pdf-lib is needed for the agentSigImage variable type.
Check prepare-document.ts existing imports — PDFDocument, rgb, StandardFonts, etc. are already
imported from @cantoo/pdf-lib. Add PDFImage to that import if not already present.
Task 1: preparePdf() — add agentSignatureData param and embed at agent-sig field coordinates
teressa-copeland-homes/src/lib/pdf/prepare-document.ts
Two targeted changes to `preparePdf()`:
**1. Add `agentSignatureData` parameter** with a default of `null` so existing call sites compile without change until the prepare route is updated in Task 2:
```typescript
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null, // ADD THIS
): Promise
```
**2. Embed the agent signature PNG once before the field loop:**
After `const pages = pdfDoc.getPages();` and before the field loop, add:
```typescript
// Embed agent signature image once — reused across all agent-sig fields
let agentSigImage: import('@cantoo/pdf-lib').PDFImage | null = null;
if (agentSignatureData) {
agentSigImage = await pdfDoc.embedPng(agentSignatureData);
}
```
If PDFImage is already imported from @cantoo/pdf-lib at the top of the file (check the existing imports), use the named import directly instead of the inline import type. Only add `PDFImage` to the destructured import if it is not already there — do not change any other imports.
**3. Replace the agent-signature stub** in the field loop with real embedding:
Find the existing `else if (fieldType === 'agent-signature') { // Skip — ... }` block and replace it with:
```typescript
} else if (fieldType === 'agent-signature') {
if (agentSigImage) {
page.drawImage(agentSigImage, {
x: field.x,
y: field.y,
width: field.width,
height: field.height,
});
}
// If no signature saved: the prepare route guards against this with 422 before calling preparePdf
}
```
No other changes to prepare-document.ts. The function body, other field type branches, and the atomic write logic remain untouched.
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20
TypeScript compiles clean; preparePdf() function signature now has 5 parameters (last is optional); agent-signature stub replaced with drawImage call; no regressions in other field type branches.
Task 2: prepare route — fetch agentSignatureData, 422 guard, pass to preparePdf
teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts
Three additions to the POST handler in `src/app/api/documents/[id]/prepare/route.ts`:
**1. Import `users` from schema** (if not already imported — check existing imports at top of file):
```typescript
import { users } from '@/lib/db/schema';
```
Also ensure `getFieldType` is imported from `@/lib/db/schema` (needed for the guard check). If already present, do not duplicate.
**2. Fetch agentSignatureData from DB** — add this block AFTER the existing doc lookup and sigFields parsing, BEFORE the `preparePdf()` call:
```typescript
// Fetch agent's saved signature for embedding at agent-signature field coordinates
const agentUser = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
columns: { agentSignatureData: true },
});
const agentSignatureData = agentUser?.agentSignatureData ?? null;
// Guard: if document has agent-signature fields but no signature saved, block prepare
const hasAgentSigFields = sigFields.some(f => getFieldType(f) === 'agent-signature');
if (hasAgentSigFields && !agentSignatureData) {
return Response.json(
{ error: 'agent-signature-missing', message: 'No agent signature saved. Go to Profile to save your signature first.' },
{ status: 422 }
);
}
```
**3. Pass `agentSignatureData` to `preparePdf()`** — update the existing call:
```typescript
// BEFORE (4 args):
await preparePdf(srcPath, destPath, textFields, sigFields);
// AFTER (5 args):
await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData);
```
Do not change any other logic in the route handler — the DB update, audit logging, and response all remain unchanged.
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20
TypeScript compiles clean; prepare route fetches agentSignatureData; guard returns 422 with { error: 'agent-signature-missing' } when agent-sig fields exist but no signature saved; preparePdf called with 5 args including agentSignatureData.
After both tasks complete, verify the full Plan 02 state:
1. `npx tsc --noEmit` passes with zero errors
2. `npm run build` succeeds (or `npm run dev` starts clean)
3. Functional test — draw save place prepare round-trip:
a. Visit /portal/profile, draw and save a signature
b. Open a document in the portal, place an "Agent Signature" field token on any page
c. Fill text fields and click Prepare
d. Prepare succeeds (200 response)
e. Download the prepared PDF and verify the agent signature PNG is embedded at the correct position
4. Guard test — prepare with no saved signature:
a. Use a fresh test account (or temporarily clear agentSignatureData in DB)
b. Place an agent-signature field, attempt to prepare
c. Prepare route returns 422 with `{ error: 'agent-signature-missing' }`
5. Client signing page test — open the signing link for the prepared document:
a. Signing page does NOT show an agent-signature field overlay (isClientVisibleField already filters it)
b. Client can sign normally — only client-signature and initials overlays appear
- AGENT-04: Agent's saved PNG is embedded at each agent-signature field coordinate in the prepared PDF
- No agent-signature content is exposed to the client via GET /api/sign/[token]
- Prepare fails with actionable 422 when agent-sig fields exist but no signature saved
- TypeScript build clean; zero new npm packages; no regressions on other field types