Skip to content

docs: Add effect-esign specification#32

Merged
bjacobso merged 1 commit intomainfrom
esign-spec
Oct 30, 2025
Merged

docs: Add effect-esign specification#32
bjacobso merged 1 commit intomainfrom
esign-spec

Conversation

@bjacobso
Copy link
Owner

Summary

Introduces effect-esign - a comprehensive specification for vendorable electronic signature primitives built with Effect-TS.

What's Included

11 Core & Integration Components (~600 lines total):

  • Signature capture (canvas, typed text, image upload)
  • Cryptographic signing (Web Crypto API, ECDSA P-256)
  • Document state machine (draft → pending → signed → completed)
  • Audit trail (immutable event log for compliance)
  • PDF field parsing and signature application
  • Signing session management
  • ESIGN Act consent tracking
  • Database schema (Prisma)
  • Framework integrations (Vite HttpApi, Remix, Prisma)

Key Features

Legal Compliance

  • US ESIGN Act (consent, intent, attribution, retention)
  • EU eIDAS Simple Electronic Signatures
  • Complete audit trail for regulatory requirements

Security

  • Cryptographic signing with Web Crypto API
  • Session tokens (256-bit entropy)
  • IP + User Agent tracking
  • Biometric capture (drawing speed, pressure)

Architecture

  • Effect Services for all operations
  • Schema validation everywhere
  • Event sourcing for audit trail
  • State machine for workflow management

Design Philosophy

Follows Meta Effect's vendorable component approach:

  • Users copy components directly into their codebase
  • Fully customizable (~50-100 lines per component)
  • Educational - learn Effect patterns by example
  • No npm package or framework overhead

Implementation Status

  • Phase 1: Core primitives spec (this PR)
  • Phase 2: Integration components (Vite, Remix, Prisma)
  • Phase 3: Advanced features (templates, bulk send, biometrics)
  • Phase 4: Compliance enhancements (eIDAS AES, timestamping)

Usage Example

// Create document
const doc = yield* stateMachine.create({
  documentId: crypto.randomUUID(),
  title: "Employment Agreement",
  signers: [{ userId: "user_1", email: "john@example.com", role: "employee", order: 1 }]
})

// Send for signing
yield* stateMachine.transition(doc, "send")

// Capture signature
const signature = yield* capture.fromCanvas(canvasElement)

// Apply to PDF
const signedPdf = yield* pdfSigner.applySignature(pdfBytes, { field, signature })

// Generate audit report
const report = yield* audit.generateReport(doc.documentId)

Related

This spec complements existing Meta Effect specs:


🤖 Generated with Claude Code

Introduces comprehensive specification for vendorable e-signature primitives including signature capture, cryptographic operations, document state management, audit trails, and compliance tracking for ESIGN Act and eIDAS standards.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 30, 2025

Pull Request Review: effect-esign Specification

Overall Assessment

This is an excellent, comprehensive specification for electronic signature primitives. The document demonstrates thorough understanding of legal compliance requirements, Effect-TS patterns, and Meta Effect's vendorable component philosophy. The scope is ambitious yet well-structured.

Recommendation: ✅ Approve with minor suggestions


Strengths

1. Exceptional Compliance Coverage

  • Thorough treatment of US ESIGN Act requirements (lines 993-1017)
  • Clear distinction between eIDAS tiers (SES/AES/QES) (lines 1018-1030)
  • Comprehensive audit trail design with immutable event sourcing
  • Security considerations section addresses real-world concerns (lines 959-990)

2. Excellent Effect-TS Patterns

  • Proper use of Effect Services throughout all components
  • Schema-first approach with Effect Schema for all data types
  • Effect.gen for composable workflows
  • Clear separation between pure functions and Effect programs

3. Well-Scoped Component Design

  • Each component targets ~50-100 lines (per Meta Effect philosophy)
  • Clear boundaries between components
  • Integration components demonstrate real framework usage (Vite, Remix, Prisma)
  • "Not Included" section (lines 26-30) sets clear expectations

4. Strong Documentation

  • Code examples for every component show actual usage patterns
  • Schema types included inline with component descriptions
  • Usage patterns section (lines 853-957) demonstrates composition
  • Related documents and external references provided

Issues & Concerns

🔴 Critical Issues

1. Missing "Copy this file into your project" Footer in Examples

Location: Throughout all component code examples

Issue: Per CLAUDE.md lines 95-96, all vendorable components MUST include the footer comment:

/**
 * Copy this file into your project and customize for your needs.
 */

The spec's code examples don't show this footer. When implementing Phase 1, ensure ALL component files include this.

Impact: High - The registry generator requires this footer (see CLAUDE.md lines 189-195)


2. Schema.Class vs Schema.Struct Inconsistency

Location: Multiple schema definitions throughout

Issue: The spec uses Schema.Class for all schemas:

export class DrawSignature extends Schema.Class<DrawSignature>()({ ... }) {}

But also uses Schema.Struct in some contexts (e.g., line 576-583 in API payload definitions).

Question: Should simple data structures use Schema.Struct instead of Schema.Class? Classes add branding and methods but increase complexity. For vendorable 50-line components, plain structs might be more appropriate.

Recommendation: Establish a consistent pattern:

  • Use Schema.Class for domain entities with behavior (Document, Signer)
  • Use Schema.Struct for simple DTOs (metadata, API payloads)

3. CryptoKey Cannot Be Schema Validated

Location: Lines 137-141

Issue:

export class SignatureKeyPair extends Schema.Class<SignatureKeyPair>()({
  publicKey: Schema.InstanceOf(CryptoKey),
  privateKey: Schema.InstanceOf(CryptoKey)
}) {}

Problem: CryptoKey objects are not serializable and cannot cross process boundaries. This schema cannot be encoded/decoded, which breaks Effect Schema's core value proposition.

Recommendation: Either:

  1. Store as Uint8Array (exported key bytes) in schemas
  2. Make this a TypeScript type (not Effect Schema) for in-memory use only
  3. Create separate "storage" schemas with serializable types

⚠️ Major Issues

4. State Machine Transition API Design

Location: Lines 184-199

Issue: The transition API is unclear about mutability:

const partiallySigned = yield* stateMachine.transition(pending, "sign", { ... })

Questions:

  • Does transition mutate pending or return a new document?
  • How does the state machine persist changes (database, in-memory)?
  • What happens if two signers try to sign simultaneously?

Recommendation: Add clarifying comments in the spec:

// Returns new immutable Document. Original 'pending' unchanged.
// Persistence handled by DocumentStateMachine service implementation.
// Optimistic concurrency control via updatedAt timestamps.

5. Missing Error Handling in Examples

Location: Throughout usage examples (lines 855-931)

Issue: No examples show error handling patterns. E-signature workflows have many failure modes:

  • Session expired
  • Signer already signed
  • Invalid signature format
  • Network failures during PDF upload

Recommendation: Add one complete example showing Effect error handling:

const program = Effect.gen(function*() {
  const signature = yield* capture.fromCanvas(canvasElement)
}).pipe(
  Effect.catchTag("SessionExpired", (e) => Effect.fail(new UserFacingError("Session expired"))),
  Effect.catchTag("ValidationError", (e) => Effect.fail(new UserFacingError("Invalid signature"))),
  Effect.retry(Schedule.exponential(1000))  // Retry network errors
)

6. Prisma Schema Not Vendorable

Location: Lines 724-851

Issue: Prisma schemas (.prisma files) don't fit the vendorable TypeScript component model. They:

  • Require compilation via prisma generate
  • Must live in specific file locations
  • Can't be "copied into your project" like TypeScript files

Questions:

  • How do users vendor the Prisma schema? Copy-paste into their existing schema?
  • What about naming conflicts with existing models?
  • Should this be a separate "integration guide" instead of a "component"?

Recommendation: Consider restructuring as:

  • Component: prisma-signature-adapter.ts (TypeScript Effect Service wrapping Prisma client)
  • Integration Guide: Separate doc explaining how to merge schema into existing Prisma setup

💡 Suggestions (Nice to Have)

7. Add Implementation Status to Roadmap

Location: Lines 1050-1084

Suggestion: The roadmap shows checkmarks for Phase 1 and 2, but Status is "Planned" (line 3). This is confusing.

Recommendation: Either:

  • Change status to "Draft" or "Specification Complete"
  • Remove checkmarks (use [ ] for all unimplemented items)
  • Add dates: [x] 2025-10-29: signature-capture.ts

8. Security: Session Token HMAC Details Missing

Location: Lines 450-455

Issue: The spec mentions "Include HMAC to prevent token tampering" but provides no details on:

  • What is HMACed (token + documentId + signerId?)
  • Where is the HMAC stored (in token itself? separate field?)
  • What's the secret key source?

Recommendation: Add a security implementation note:

// Session token format: base64(random_bytes || HMAC-SHA256(random_bytes || documentId || signerId, secret))
// Secret from environment: SESSION_TOKEN_SECRET
// Validation: recompute HMAC and constant-time compare

9. Performance: Consider Signature Image Compression

Location: Lines 64-98 (DrawSignature schema)

Issue: Storing full PNG data URLs can be large (50-200KB per signature). For high-volume systems, this adds up quickly.

Suggestion: Add performance note:

// Performance: Consider compressing signature images
// - WebP format (50% smaller than PNG)
// - Reduce dimensions to 600x200 (sufficient for most displays)
// - Store thumbnail + full-resolution separately

10. Add Biometric Capture Example

Location: Lines 1102 (Open Questions)

Issue: The spec mentions biometric data ("drawing speed, pressure") as a future feature, but this is valuable NOW for legal defensibility.

Suggestion: Add optional biometric fields to DrawSignature.metadata:

metadata: Schema.Struct({
  width: Schema.Number,
  height: Schema.Number,
  strokeCount: Schema.Number,
  durationMs: Schema.Number,
  // Optional biometric data (increases legal weight)
  strokes: Schema.optional(Schema.Array(Schema.Struct({
    points: Schema.Array(Schema.Tuple(Schema.Number, Schema.Number)),
    timestamps: Schema.Array(Schema.Number),  // milliseconds
    pressure: Schema.optional(Schema.Array(Schema.Number))  // 0-1
  })))
})

Modern browsers support this via PointerEvents API. Minimal implementation cost for significant compliance value.


Code Quality

✅ Excellent

  • Effect-first architecture throughout
  • Clear separation of concerns (capture, crypto, state, audit)
  • Schema validation at boundaries
  • Immutable data structures
  • Service pattern for dependency injection

✅ Best Practices Followed

  • Uses official Effect libraries (@effect/platform, @effect/schema)
  • Proper use of Effect.gen (not .pipe chains)
  • Tagged union types for variants (DrawSignature | TypeSignature | UploadSignature)
  • Append-only audit log design

📝 Minor Style Issues

  • None significant. Code examples are clear and idiomatic.

Security Assessment

✅ Good Security Practices

  • Cryptographically secure session tokens (256-bit)
  • Session expiration and one-time use
  • Web Crypto API (battle-tested, audited)
  • IP address and User Agent tracking for attribution
  • Immutable audit trail

⚠️ Security Gaps

  1. Missing rate limiting: No mention of protecting signing endpoints from abuse
  2. No mention of CSP: Content Security Policy for signature canvas
  3. Private key storage vague: "IndexedDB or KMS" - need more guidance on key lifecycle
  4. No mention of signature image sanitization: Uploaded images could contain malicious metadata

Recommendations:

  • Add rate limiting to signing sessions (e.g., 5 sessions per user per hour)
  • Document CSP requirements for canvas-based capture
  • Add key rotation strategy (even if keys are per-document)
  • Sanitize uploaded images (strip EXIF, re-encode)

Testing & Quality Assurance

⚠️ Missing Test Strategy

Issue: The spec mentions tests in CLAUDE.md context but doesn't outline test approach for e-sign components.

Recommendation: Add a "Testing Strategy" section:

## Testing Strategy

### Registry Maintainers (Pre-Vendor)
- Unit tests for pure functions (transforms, validation)
- Effect.gen tests using @effect/vitest
- Mock external dependencies (Web Crypto, Canvas API)
- Test legal compliance invariants

### Component Users (Post-Vendor)
- Users add tests in their own test suite
- Examples in component headers serve as test inspiration
- Focus on integration tests (full signing flow)

Reference: See CLAUDE.md lines 283-298 for existing testing patterns.


Performance Review

✅ Reasonable Performance Targets

Lines 1085-1098 provide realistic benchmarks:

  • Signature capture: <100ms ✅
  • Crypto ops: 10-50ms ✅
  • PDF parsing: 200-500ms ⚠️ (could be slower for large files)

💡 Optimization Suggestions

  1. PDF parsing: Recommend Web Workers by default (not just as tip)
  2. Audit batching: Provide example batch API (current examples show one-at-a-time)
  3. Session pre-generation: Show example of pre-generating sessions during document creation

Documentation Quality

✅ Excellent Documentation

  • Clear overview with scope boundaries
  • Every component has usage example
  • Schema types shown inline
  • Security and compliance sections comprehensive
  • Related documents linked

📝 Minor Improvements Needed

  1. Add a "Quick Start" section showing end-to-end flow in 30 lines
  2. Add troubleshooting section (common errors and solutions)
  3. Add migration guide from existing e-sign solutions
  4. Clarify relationship to effect-forms (line 1114) - how do they compose?

Compliance Review

✅ Excellent Legal Understanding

The spec demonstrates strong understanding of:

  • US ESIGN Act requirements (consent, intent, attribution, retention)
  • EU eIDAS tiers and limitations
  • Audit requirements for regulatory compliance

💡 Enhancements

  1. Add compliance checklist: Provide users a PDF checklist of ESIGN Act requirements to verify
  2. Add sample disclosure text: Show example ESIGN Act disclosure text users can customize
  3. Add retention policy guidance: How long must signatures be kept? (varies by jurisdiction)
  4. Link to DocuSign/Adobe Sign compliance docs: Users trust established vendors - show parity

Final Recommendations

Before Merging (Spec Review)

  • Fix: Add note about "Copy this file" footer requirement
  • Fix: Clarify CryptoKey serialization issue (line 137-141)
  • Fix: Document state machine mutability/persistence model
  • Consider: Restructure Prisma component as integration guide
  • Consider: Add error handling example

Phase 1 Implementation

  • Ensure all components include required JSDoc footer
  • Add comprehensive tests using @effect/vitest
  • Add security sanitization for uploaded images
  • Add rate limiting to session creation
  • Implement biometric capture in DrawSignature (optional but valuable)

Phase 2 Enhancements

  • Add "Quick Start" guide
  • Add troubleshooting section
  • Add compliance checklist for users
  • Add sample ESIGN Act disclosure text

Conclusion

This specification is publication-ready with minor clarifications. The architecture is sound, the compliance understanding is excellent, and the Effect-TS patterns are idiomatic. The vendorable component approach is well-suited to e-signatures, where customization is essential.

The main concerns are:

  1. CryptoKey serialization (technical blocker)
  2. State machine persistence model (needs clarification)
  3. Prisma schema vendoring (conceptual mismatch)

Once these are addressed, this will be a valuable addition to the Meta Effect registry.

Great work on a complex, legally-sensitive domain! 🎉


🤖 Generated with Claude Code

@bjacobso bjacobso merged commit 00abf0a into main Oct 30, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant