From a4bad723bc027842d7b4e3491332b1241aeaecf3 Mon Sep 17 00:00:00 2001 From: justin Date: Sun, 7 Jun 2026 04:44:11 -0500 Subject: [PATCH] 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 --- api/migrations/092_esign_ink_consent.sql | 28 +++ api/src/routes/esign-ink-consent.ts | 59 +++++ api/src/routes/portal-esign-generic.ts | 45 +++- api/test/test_esign_ink_consent.ts | 58 +++++ .../patent-risk-mechanical-wet-signature.md | 214 ++++++++++++++++++ scripts/workers/services/npi_provider.py | 2 +- site/public/portal/esign/index.html | 51 ++++- 7 files changed, 452 insertions(+), 5 deletions(-) create mode 100644 api/migrations/092_esign_ink_consent.sql create mode 100644 api/src/routes/esign-ink-consent.ts create mode 100644 api/test/test_esign_ink_consent.ts create mode 100644 docs/legal/patent-risk-mechanical-wet-signature.md diff --git a/api/migrations/092_esign_ink_consent.sql b/api/migrations/092_esign_ink_consent.sql new file mode 100644 index 0000000..2e623ec --- /dev/null +++ b/api/migrations/092_esign_ink_consent.sql @@ -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).'; diff --git a/api/src/routes/esign-ink-consent.ts b/api/src/routes/esign-ink-consent.ts new file mode 100644 index 0000000..7a2bd3b --- /dev/null +++ b/api/src/routes/esign-ink-consent.ts @@ -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).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; +} diff --git a/api/src/routes/portal-esign-generic.ts b/api/src/routes/portal-esign-generic.ts index 0f75956..767d8e9 100644 --- a/api/src/routes/portal-esign-generic.ts +++ b/api/src/routes/portal-esign-generic.ts @@ -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, ], ); diff --git a/api/test/test_esign_ink_consent.ts b/api/test/test_esign_ink_consent.ts new file mode 100644 index 0000000..d7abde9 --- /dev/null +++ b/api/test/test_esign_ink_consent.ts @@ -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`); diff --git a/docs/legal/patent-risk-mechanical-wet-signature.md b/docs/legal/patent-risk-mechanical-wet-signature.md new file mode 100644 index 0000000..58d6bef --- /dev/null +++ b/docs/legal/patent-risk-mechanical-wet-signature.md @@ -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 `` 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. diff --git a/scripts/workers/services/npi_provider.py b/scripts/workers/services/npi_provider.py index a14fa65..8b9b242 100644 --- a/scripts/workers/services/npi_provider.py +++ b/scripts/workers/services/npi_provider.py @@ -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 diff --git a/site/public/portal/esign/index.html b/site/public/portal/esign/index.html index fb8a766..cdbef6a 100644 --- a/site/public/portal/esign/index.html +++ b/site/public/portal/esign/index.html @@ -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)} @@ -76,6 +78,19 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l

Step 2 — Your Signature

+ + + + +
@@ -95,6 +110,7 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
+
@@ -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();