esign: ink-reproduction consent gate + patent-risk research

Consent gate (the legal linchpin from the wet-signature memo):
- migration 092 adds ink_consent/ink_consent_at/ink_consent_text to esign_records
- extract pure, unit-tested gate logic into esign-ink-consent.ts (DRY single
  source for route + signing page): isInkReproduction / inkConsentRequired /
  inkConsentSatisfied + verbatim client-safe INK_CONSENT_TEXT
- portal-esign-generic.ts: GET surfaces ink_reproduction + consent text; POST
  gates DRAWN signatures on ink-path docs on explicit consent, stores it
- signing page locks the signature block until consent is checked (drawn only)
- npi_provider marks cms855/cms10114 esign metadata ink_reproduction=true
- 33 unit checks: gate truth table + consent text omits all internal mechanics
  (plotter/machine/CMS/MAC/etc) and keeps required legal reassurances

Patent-risk memo (docs/legal/patent-risk-mechanical-wet-signature.md):
- prior-art-dated risk analysis (autopen 1803/1942, plotters, CNC = public domain
  => low risk on core concept; e-sign workflow space litigious)
- firsthand recent-grant sweep (1.58M USPTO grants 2021-2025, queried via DuckDB):
  ZERO patents on machine-applies-signature-in-ink; e-sign players hold only
  electronic-workflow patents. Not an FTO; flags where attorney search is needed
This commit is contained in:
justin 2026-06-07 04:44:11 -05:00
parent f8d2a7f01f
commit a4bad723bc
7 changed files with 452 additions and 5 deletions

View file

@ -0,0 +1,28 @@
-- 092: Ink-reproduction consent on signature records.
--
-- The Standard (no-login) CMS filing path reproduces the signer's OWN captured
-- signature strokes in real ink on the printed form (pen plotter) so the mailed
-- application carries an original ink signature. Per the legal-risk research
-- (docs/legal/remote-mechanical-wet-signature-precedent.md), the linchpin that
-- keeps this on the valid side of the forgery/agency line is an EXPLICIT,
-- per-document authorization from the signer to reproduce their signature in ink
-- on this specific document.
--
-- These columns capture that consent at signing time, alongside the existing
-- perjury attestation. They are only meaningful for drawn signatures on ink-path
-- documents (metadata.ink_reproduction = true); other docs leave them false/NULL.
--
-- Idempotent.
ALTER TABLE esign_records
ADD COLUMN IF NOT EXISTS ink_consent BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS ink_consent_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS ink_consent_text TEXT;
COMMENT ON COLUMN esign_records.ink_consent IS
'TRUE when the signer expressly authorized reproducing their drawn signature '
'in ink on this document (pen-plotter path). Captured at signing time.';
COMMENT ON COLUMN esign_records.ink_consent_at IS
'When the ink-reproduction consent was given (signer-side timestamp).';
COMMENT ON COLUMN esign_records.ink_consent_text IS
'Verbatim consent language the signer agreed to (for the audit trail).';

View file

@ -0,0 +1,59 @@
/**
* Pure helpers for the ink-reproduction consent gate (DB-free, unit-tested).
*
* The Standard (no-login) CMS filing path reproduces the signer's OWN captured
* signature strokes in real ink on the printed form (pen plotter). The legal
* linchpin (docs/legal/remote-mechanical-wet-signature-precedent.md) is an
* EXPLICIT, per-document authorization to reproduce the signature in ink. These
* helpers decide when that consent is required and whether it was satisfied, so
* the route and the signing page agree on one source of truth.
*/
/** Verbatim consent the signer must agree to before drawing an ink-path signature.
*
* Client-safe (no internal mechanics) but legally explicit: authorizes Performance
* West to reproduce THIS signer's OWN drawn signature in ink ONE TIME on THIS
* single document. The "only once / this one form / not reused" language reassures
* the signer the signature is not stored for reuse. Stored verbatim with the
* signature for the audit trail.
*/
export const INK_CONSENT_TEXT =
"I understand this filing must be submitted on the official paper form with an " +
"original ink signature. I authorize Performance West Inc. to reproduce my own " +
"signature, exactly as I draw it below, in ink one time on this single form, and " +
"to submit it on my behalf. My signature will be used solely to complete this " +
"filing and will not be reused for any other document or purpose. The signature " +
"applied will be my own signature, made with my authorization and with the " +
"intent to sign this document.";
/** Is this document on the ink-reproduction path? (metadata.ink_reproduction). */
export function isInkReproduction(documentMetadata: unknown): boolean {
return !!documentMetadata
&& typeof documentMetadata === "object"
&& (documentMetadata as Record<string, unknown>).ink_reproduction === true;
}
/**
* Does this signing attempt REQUIRE the ink-reproduction consent?
* Only a DRAWN signature on an ink-path document needs it (a typed signature is
* not reproduced as the signer's own hand, so it is exempt).
*/
export function inkConsentRequired(
documentMetadata: unknown,
signatureType: "drawn" | "typed" | string | undefined,
): boolean {
return isInkReproduction(documentMetadata) && signatureType === "drawn";
}
/**
* Is the consent satisfied for this attempt? True when not required, or when
* required and the signer explicitly gave it (ink_consent === true).
*/
export function inkConsentSatisfied(
documentMetadata: unknown,
signatureType: "drawn" | "typed" | string | undefined,
inkConsent: unknown,
): boolean {
if (!inkConsentRequired(documentMetadata, signatureType)) return true;
return inkConsent === true;
}

View file

@ -22,6 +22,11 @@
import { Router, type Request, type Response } from "express";
import { pool } from "../db.js";
import { requirePortalAuth } from "../middleware/portalAuth.js";
import {
INK_CONSENT_TEXT,
isInkReproduction,
inkConsentSatisfied,
} from "./esign-ink-consent.js";
const router = Router();
const WORKER_URL = process.env.WORKER_URL || "http://workers:8090";
@ -93,12 +98,21 @@ router.get("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
// Pull order number for subtitle (e.g. "CO-ABC12345")
const metadata = rec.document_metadata || {};
// Ink-reproduction path: when this document's signature will be reproduced in
// real ink on the printed form (pen-plotter / Standard CMS filing path), the
// signing page must collect an explicit, per-document consent to reproduce the
// drawn signature in ink BEFORE capturing it. See
// docs/legal/remote-mechanical-wet-signature-precedent.md.
const inkReproduction = isInkReproduction(metadata);
res.json({
document_title: rec.document_title,
entity_name: rec.entity_name,
order_number: rec.order_number,
document_url: documentUrl,
requires_perjury: rec.requires_perjury || false,
ink_reproduction: inkReproduction,
ink_consent_text: inkReproduction ? INK_CONSENT_TEXT : null,
metadata,
});
} catch (err) {
@ -116,10 +130,11 @@ router.get("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res: Response) => {
const { order_id: orderNumber, order_type: documentType, email } = req.portalAuth!;
const { signature, agreed_at, user_agent } = req.body as {
const { signature, agreed_at, user_agent, ink_consent } = req.body as {
signature?: { type: "drawn" | "typed"; image_b64?: string; name?: string; vector?: any };
agreed_at?: string;
user_agent?: string;
ink_consent?: boolean;
};
// Validate signature
@ -146,7 +161,8 @@ router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
try {
// Find the pending record
const { rows } = await pool.query(
`SELECT id, order_number, document_type, status, requires_perjury
`SELECT id, order_number, document_type, status, requires_perjury,
document_metadata, signed_at
FROM esign_records
WHERE order_number = $1
AND document_type = $2
@ -169,6 +185,19 @@ router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
return;
}
// Ink-reproduction gate: if this document's signature will be reproduced in
// ink on the official paper form, a drawn signature REQUIRES the explicit
// per-document ink-reproduction consent (the legal linchpin — see
// docs/legal/remote-mechanical-wet-signature-precedent.md). A typed signature
// is not reproduced as the signer's own hand, so it does not need this gate.
const inkReproduction = isInkReproduction(rec.document_metadata);
if (!inkConsentSatisfied(rec.document_metadata, signature.type, ink_consent)) {
res.status(400).json({
error: "Please confirm the authorization to use your signature on the official form.",
});
return;
}
// Store the signature
const sigData = signature.type === "drawn"
? signature.image_b64!.replace(/^data:image\/png;base64,/, "")
@ -194,6 +223,10 @@ router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
const clientIp = (req as any).clientIp || req.ip || "";
const signedAt = new Date().toISOString();
// Record the ink-reproduction consent (only meaningful when this document is
// on the ink-reproduction path and the signer drew their signature).
const inkConsentGiven = inkReproduction && signature.type === "drawn" && ink_consent === true;
await pool.query(
`UPDATE esign_records
SET status = 'signed',
@ -205,8 +238,11 @@ router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
signer_user_agent = $6,
signed_at = $7,
perjury_agreed = $8,
ink_consent = $9,
ink_consent_at = $10,
ink_consent_text = $11,
updated_at = NOW()
WHERE id = $9`,
WHERE id = $12`,
[
signature.type,
sigData,
@ -216,6 +252,9 @@ router.post("/api/v1/portal/esign", requirePortalAuth, async (req: Request, res:
user_agent || "",
signedAt,
rec.requires_perjury ? true : false,
inkConsentGiven,
inkConsentGiven ? signedAt : null,
inkConsentGiven ? INK_CONSENT_TEXT : null,
rec.id,
],
);

View file

@ -0,0 +1,58 @@
/**
* Unit tests for the ink-reproduction consent gate (pure logic, no DB).
* Run: npx tsx api/test/test_esign_ink_consent.ts
*/
import assert from "node:assert";
import {
INK_CONSENT_TEXT,
isInkReproduction,
inkConsentRequired,
inkConsentSatisfied,
} from "../src/routes/esign-ink-consent.js";
let pass = 0;
const ok = (name: string, cond: boolean) => {
assert.ok(cond, name);
pass++;
};
// --- isInkReproduction ---
ok("ink meta true", isInkReproduction({ ink_reproduction: true }) === true);
ok("ink meta false", isInkReproduction({ ink_reproduction: false }) === false);
ok("ink meta missing", isInkReproduction({}) === false);
ok("ink meta null", isInkReproduction(null) === false);
ok("ink meta undefined", isInkReproduction(undefined) === false);
ok("ink meta string-truthy not enough", isInkReproduction({ ink_reproduction: "true" }) === false);
// --- inkConsentRequired: only DRAWN on ink-path docs ---
ok("required: drawn + ink", inkConsentRequired({ ink_reproduction: true }, "drawn") === true);
ok("not required: typed + ink", inkConsentRequired({ ink_reproduction: true }, "typed") === false);
ok("not required: drawn + non-ink", inkConsentRequired({ ink_reproduction: false }, "drawn") === false);
ok("not required: drawn + no meta", inkConsentRequired({}, "drawn") === false);
ok("not required: undefined type", inkConsentRequired({ ink_reproduction: true }, undefined) === false);
// --- inkConsentSatisfied ---
// exempt cases (consent not required) are always satisfied regardless of flag
ok("satisfied: typed exempt (no consent)", inkConsentSatisfied({ ink_reproduction: true }, "typed", undefined) === true);
ok("satisfied: non-ink drawn exempt", inkConsentSatisfied({ ink_reproduction: false }, "drawn", undefined) === true);
ok("satisfied: no-meta drawn exempt", inkConsentSatisfied({}, "drawn", false) === true);
// required cases: must have ink_consent === true
ok("blocked: drawn+ink, no consent", inkConsentSatisfied({ ink_reproduction: true }, "drawn", undefined) === false);
ok("blocked: drawn+ink, consent false", inkConsentSatisfied({ ink_reproduction: true }, "drawn", false) === false);
ok("blocked: drawn+ink, consent truthy-but-not-true", inkConsentSatisfied({ ink_reproduction: true }, "drawn", "true") === false);
ok("blocked: drawn+ink, consent 1 (not strict true)", inkConsentSatisfied({ ink_reproduction: true }, "drawn", 1) === false);
ok("allowed: drawn+ink, consent true", inkConsentSatisfied({ ink_reproduction: true }, "drawn", true) === true);
// --- consent text: client-safe (no internal mechanics) ---
const banned = ["plotter", "machine", "CMS", "855", "10114", "MAC", "Baltimore", "PO Box", "robot"];
for (const w of banned) {
ok(`consent text omits "${w}"`, !INK_CONSENT_TEXT.toLowerCase().includes(w.toLowerCase()));
}
// --- consent text: legally required reassurances present ---
ok("consent says 'ink one time'", INK_CONSENT_TEXT.includes("in ink one time"));
ok("consent says 'single form'", INK_CONSENT_TEXT.includes("single form"));
ok("consent says 'not be reused'", INK_CONSENT_TEXT.includes("will not be reused"));
ok("consent says 'my own signature'", INK_CONSENT_TEXT.includes("my own signature"));
ok("consent says 'intent to sign'", INK_CONSENT_TEXT.includes("intent to sign"));
console.log(`\nesign ink-consent: ${pass} checks passed`);

View file

@ -0,0 +1,214 @@
# Patent-risk analysis: remote signature capture + mechanical wet-ink reproduction
Status: internal patent-RISK research. **Not legal advice and not a freedom-to-
operate (FTO) opinion.** A real FTO requires a patent attorney to search live
claims and read them against our implementation. This memo identifies where risk
is low (mature/expired prior art) vs. where a targeted FTO search is warranted.
Companion docs: `docs/legal/remote-mechanical-wet-signature-precedent.md`,
`docs/plans/remote-wet-signature-products.md`.
What we do (the accused "system," broken into components):
1. Capture a hand-drawn signature in a browser as **vector strokes** (x, y, t).
2. Store it + an audit trail (`esign_records`), send a JWT no-login signing link.
3. Generate/fill a PDF form; stamp a digital signature image (existing path).
4. **Reproduce the captured strokes in real ink on paper with a pen plotter /
Line-us arm** (the novel-feeling part).
5. Mail/file the document; track fulfillment.
## TL;DR
- **Components 1-3 and 5** are ubiquitous, decades-old techniques (signature
capture, e-sign workflow, PDF fill, mailing automation). **Patent risk: low**,
but the *e-signature workflow* space is litigious (DocuSign, RPost, etc.), so a
targeted FTO on any **specific workflow feature** is prudent before scaling.
- **Component 4** (drawing a signature in ink with a machine) sits on **very old,
expired prior art**: signature-duplicating machines (Hawkins polygraph, 1803),
autopens (1930s-1942), pen plotters (1950s-1980s), CNC/G-code motion (1950s+).
The *idea* of a machine writing a signature is firmly in the public domain.
**Patent risk on the core concept: low.**
- The realistic risk is **not** the broad concept but **narrow, recent claims**
on a *specific combination/method* (e.g. "capture an e-signature online, then
transmit it to a robotic pen to apply wet ink for a regulated filing"). A
firsthand title sweep of 1.58M recent grants (2021-2025) found **no such
patent**; the e-sign players (DocuSign/Adobe/Silanis) hold only *electronic*
workflow patents. This reduces, but does not eliminate, the need for an
attorney FTO (which must also cover pre-2021 and pending claims).
## 1. Signature capture (vector strokes in a browser)
- **Prior art is overwhelming and old.** Electronic signature pads (Topaz,
Wacom), tablet/stylus capture, and HTML5 `<canvas>` signature capture have been
standard for 15-25+ years. Capturing (x, y, t) stroke data is basic digitizer
technique predating the web.
- **Risk: low** for the act of capturing strokes. Any patent broad enough to
cover "capture a signature on a touchscreen" would almost certainly be invalid
over prior art. Avoid copying a *specific patented capture algorithm* (e.g. a
particular biometric/pressure-analysis method) - we use plain stroke capture.
## 2. E-signature workflow + audit trail + no-login link (JWT)
- **This is the litigious zone of the three software components.** Companies with
e-signature/e-delivery patent portfolios have actively asserted them:
- **RPost** (Registered Email / e-signature patents) has a history of
licensing/litigation around proof-of-delivery and e-signature.
- **DocuSign** holds many patents on signing *workflows*, "envelopes," routing,
and tamper-evidence.
- **But:** post-*Alice v. CLS Bank* (573 U.S. 208 (2014)), many abstract
"do-it-on-a-computer" software/business-method patents are vulnerable to
invalidation under 35 U.S.C. § 101. Generic e-sign workflow claims have been
narrowed/invalidated.
- **Risk: low-moderate.** Our workflow (JWT link, store signature + IP/UA/time,
resume a pipeline) is generic and well-trodden. The risk is asserting a
*specific patented feature* (e.g. a particular tamper-evidence/seal method, a
specific routing/orchestration claim). **Do a targeted FTO before building any
distinctive workflow feature** (sequential multi-party routing, a proprietary
audit-seal, etc.).
## 3. PDF generation / form fill / digital stamp
- **Ubiquitous, mature.** Filling AcroForm fields and overlaying an image onto a
PDF is standard library functionality (pypdf, reportlab, PDFBox, etc.) used by
millions of applications. **Risk: low.**
## 4. Mechanical wet-ink reproduction (pen plotter / robotic arm)
This is the component that *feels* novel but is built on the oldest prior art:
- **Signature-duplicating machines: 1803.** John Isaac Hawkins patented the
"polygraph" pantograph signature copier (used extensively by Jefferson).
- **Autopen: 1930s-1942.** The "Robot Pen" (1930s) and De Shazo's commercially
successful autopen (1942, Navy order) mass-reproduced signatures in ink. By
mid-century there were 500+ autopens in Washington.
- **Pen plotters: 1950s-1980s.** X-Y pen plotters (Calcomp 1959; HP 7470A 1980s)
draw arbitrary vector paths in ink - exactly our motion problem.
- **CNC / G-code motion control: 1950s+.** Numerically-controlled multi-axis
motion (which a CR-10/3D printer is) is decades old.
- (Sources: Wikipedia "Autopen," retrieved firsthand; well-documented hardware
history.)
- **Conclusion:** "a machine that applies a person's signature in ink by
following a stored path" is **firmly in the public domain.** No one can hold a
valid patent on the *concept*. **Risk on the core mechanism: low.**
Residual risk is only on **narrow modern improvements**, e.g.:
- A *specific* signature-reproduction algorithm (pressure/velocity modeling to
mimic natural handwriting) that someone recently patented. We use plain
fit-to-box stroke replay; avoid copying a specific patented "natural-motion"
method without checking.
- A *specific combined method claim* tying online e-signature capture to a
robotic ink applicator for filings. We found no such patent, but this is the
one area an attorney FTO should specifically probe (handwriting-robot startups,
e.g. companies in the "robot-written mail" space, may hold method patents on
*their* pipelines - those would be narrow to their implementations).
## 5. Mailing / fulfillment automation
- **Mature and generic.** Batch mailing, address routing, print-and-mail
workflows are old and widely practiced. **Risk: low.** (Note RPost-style
proof-of-delivery patents exist; we are not claiming a proprietary e-delivery
receipt mechanism.)
## Recent-grant prior-art sweep (2021-2025, firsthand query)
To probe the one real risk zone (a recent method patent tying online signature
capture to robotic ink application, and e-sign workflow patents our flow might
read on), we ran a firsthand title search against a full USPTO grant dataset
(PatentsView-derived, **1,575,344 patents granted 2021-06 to 2025-09**). This is
a **title-level** sweep of recent grants, **not** a claims-level FTO, and it does
not cover pre-2021 art - but it directly tests "did anyone recently patent our
specific combination?" The answer is essentially no:
- **Signature + (ink|pen|stylus|wet) + (robot|machine|apparatus|device): 0
relevant hits.** All matches were unrelated (wireless-comms "signatures,"
printer ink-color flushing). **No "machine applies a signature in ink" patent
appears in 5 years of grants.**
- **autopen: 0 hits. signature+plotter: 0. signature+reproduce: 0.**
- **"writing/drawing robot": only ornamental DESIGN patents** (D1090657,
D1058630 - cover a device's *appearance*, not a signing method) plus an
unrelated Yaskawa robot-withdrawal utility patent. Nothing signature-specific.
- **"handwriting + machine": only handwriting *extraction* (reading) via ML**
(12175799), i.e. the opposite of writing.
- **E-signature workflow space is owned by the expected players, all purely
electronic (no ink/paper):**
- **DocuSign, Inc.** - many recent grants on document models, recipient
routing/notification, OCR, contract generation (e.g. 12411896 "Document
graph"; RE50043 associating third-party content with online signing).
- **Adobe Inc.** - electronic-signature *capture UI* (11132105, 11159328,
11750670 "signature collection within an online conference").
- **Silanis Technology, Inc.** - 11093652 "Web-based method and system for
applying a legally enforceable signature on an electronic document" (the
single closest hit by name) - but it is **purely electronic**; it does not
apply ink to paper, so it does not read on our mechanical reproduction.
- **Tamper/seal:** 11743053 "Electronic signature system and tamper-resistant
device" - electronic; relevant only if we built a proprietary tamper-seal
(we do not).
**Interpretation:** the recent-patent landscape is consistent with the prior-art
analysis above. The litigious e-sign players hold **electronic-workflow** patents
(read them before building any distinctive workflow feature), but **no one holds
a recent patent on reproducing a captured signature in ink with a machine for a
filing** - our specific, novel-feeling combination. This *reduces* (does not
eliminate) the residual risk flagged in section 4.
**Caveats:** (1) title-only, recent-grant (2021-2025) - misses pre-2021 patents
(many e-sign core patents predate 2021) and any in-force patent whose title does
not contain these words; (2) does not read claims; (3) does not cover pending
applications. **Still not an FTO** - a patent attorney must read live claims
(including pre-2021 and pending) against our implementation before launch.
Method: queried the patent grant parquet directly via DuckDB (title LIKE
filters). Dataset coverage and the specific hits above were retrieved firsthand
2026-06-07.
## What would actually create exposure (avoid these without FTO)
1. Copying a **named competitor's specific feature** (a particular tamper-seal,
a specific multi-party routing UX, a specific "robot handwriting realism"
algorithm).
2. Implementing a **distinctive method** and marketing it as such (the more
specific/novel our claim of novelty, the more likely it overlaps a method
patent - and the more attractive we are as a target).
3. Building the **RON / e-notary** layer without checking notary-tech patents.
## Recommendations
1. **Now (low cost):** keep the implementation **generic and built on
public-domain techniques** (plain stroke capture, standard PDF libs, standard
G-code/CNC motion, autopen-class reproduction). This is our best defense - we
are practicing decades-old art.
2. **Before scaling / raising / public launch:** commission a **freedom-to-operate
search** from a patent attorney focused on two questions:
(a) any live method patent tying **online e-signature capture → robotic ink
application** for documents/filings; (b) any e-signature **workflow** patent
our specific features might read on.
3. **Defensive posture:** document our **prior-art basis** (this memo + the
hardware history) so that if anyone asserts a broad patent, we can argue
invalidity/non-infringement. Consider a **defensive publication** of our
pipeline (a public description) to create prior art preventing others from
patenting the same combination later.
4. **Do not patent-troll-bait:** avoid over-claiming novelty in marketing; frame
the service as "we file your documents with an original ink signature," not as
a patented invention.
## Honest limits of this memo
- We ran a **title-level sweep of recent grants (2021-2025)** (see section above)
but did **not** run a full **claims-level** search across all years; reading
live claims against our code requires a patent attorney. The recent-grant sweep
supports the low-risk *concept* assessment but is **not an FTO clearance.**
- Patent risk is jurisdiction- and claim-specific and changes as patents issue;
re-check before any major launch.
## Sources
- Prior-art hardware history (autopen 1803/1930s/1942, plotters, CNC):
Wikipedia "Autopen" (retrieved firsthand 2026-06-07), corroborated by
general hardware history.
- *Alice Corp. v. CLS Bank Int'l*, 573 U.S. 208 (2014) - § 101 abstract-idea
invalidity framework (well-known; confirm citation before reliance).
- Litigious-player context (RPost, DocuSign patent portfolios): general industry
knowledge plus the firsthand recent-grant sweep above (DocuSign/Adobe/Silanis
hits) - confirm specifics with counsel before relying.
- Recent-grant prior-art sweep: USPTO patent grant dataset (PatentsView-derived,
2021-2025, 1.58M patents), queried firsthand via DuckDB 2026-06-07. Specific
patent numbers cited above are real hits from that dataset.

View file

@ -375,7 +375,7 @@ class _BaseNPIHandler:
customer_name=provider,
document_minio_key=document_key,
requires_perjury=True,
metadata={"service_slug": self.SERVICE_SLUG, "npi": intake.get("npi", ""), "form_type": form_type},
metadata={"service_slug": self.SERVICE_SLUG, "npi": intake.get("npi", ""), "form_type": form_type, "ink_reproduction": True},
expires_hours=21 * 24,
)
# request_esign does not persist signature anchors; attach them so

View file

@ -52,6 +52,8 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
#loading{text-align:center;padding:4rem 1rem;color:#64748b}
#error-screen{display:none;text-align:center;padding:3rem 1rem}
.hidden{display:none}
/* Signature block locked until ink-reproduction consent is given */
#sig-block.locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}
</style>
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
@ -76,6 +78,19 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
<!-- Step 2: Sign -->
<div class="card">
<h2>Step 2 — Your Signature</h2>
<!-- Ink-reproduction consent gate (shown only for ink-path documents).
The signer must authorize use of their signature on the official form
BEFORE they can draw it. -->
<div id="ink-consent-box" class="hidden" style="padding:1rem;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;margin-bottom:1rem">
<p style="font-size:.85rem;color:#1f2937;margin:0 0 .75rem" id="ink-consent-text"></p>
<label style="display:flex;gap:.6rem;align-items:flex-start;font-size:.85rem;color:#1f2937;cursor:pointer">
<input type="checkbox" id="ink-consent-chk" style="margin-top:2px;flex-shrink:0;width:16px;height:16px;accent-color:#1e3a5f">
<span>I authorize this and confirm the signature I draw is my own.</span>
</label>
</div>
<div id="sig-block">
<div class="sig-tabs">
<button type="button" class="sig-tab active" data-mode="draw">Draw</button>
<button type="button" class="sig-tab" data-mode="type">Type</button>
@ -95,6 +110,7 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
<input type="text" id="typed-sig" placeholder="Your full name" autocomplete="name">
<div class="typed-preview" id="typed-preview"></div>
</div>
</div>
</div>
<!-- Step 3: Confirm -->
@ -190,6 +206,16 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
document.getElementById("perjury-box").classList.remove("hidden");
}
// Ink-reproduction consent gate: for documents whose signature will be
// reproduced in ink on the official paper form, require an explicit
// per-document authorization BEFORE the signer can draw. Lock the signature
// block until the consent box is checked.
if (data.ink_reproduction && data.ink_consent_text) {
document.getElementById("ink-consent-text").textContent = data.ink_consent_text;
document.getElementById("ink-consent-box").classList.remove("hidden");
document.getElementById("sig-block").classList.add("locked");
}
// PDF preview
if (data.document_url) {
document.getElementById("pdf-container").innerHTML =
@ -318,9 +344,31 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
var agreeChk = document.getElementById("agree-chk");
agreeChk.addEventListener("change", updateSubmit);
// ── Ink-reproduction consent: unlock the signature block when authorized ──
var inkConsentChk = document.getElementById("ink-consent-chk");
inkConsentChk.addEventListener("change", function() {
var data = window._esignData || {};
var needsInk = !!(data.ink_reproduction && data.ink_consent_text);
if (needsInk) {
document.getElementById("sig-block").classList.toggle("locked", !this.checked);
// If they un-check after drawing, clear the now-unauthorized capture.
if (!this.checked) document.getElementById("sig-clear").click();
}
updateSubmit();
});
function inkConsentOk() {
var data = window._esignData || {};
var needsInk = !!(data.ink_reproduction && data.ink_consent_text);
// Ink consent is only required for a DRAWN signature on an ink-path doc.
if (!needsInk || sigMode !== "draw") return true;
return inkConsentChk.checked;
}
function updateSubmit() {
var hasSignature = sigMode === "draw" ? hasSig : typedInput.value.trim().length >= 2;
document.getElementById("submit-btn").disabled = !(hasSignature && agreeChk.checked);
document.getElementById("submit-btn").disabled =
!(hasSignature && agreeChk.checked && inkConsentOk());
}
document.getElementById("submit-btn").addEventListener("click", async function() {
@ -353,6 +401,7 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
signature: signatureData,
agreed_at: new Date().toISOString(),
user_agent: navigator.userAgent,
ink_consent: inkConsentOk() && sigMode === "draw" && inkConsentChk.checked,
}),
});
var result = await resp.json();