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

@ -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();