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
58 lines
3.2 KiB
TypeScript
58 lines
3.2 KiB
TypeScript
/**
|
|
* 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`);
|