ASL Intake — Source library
Public documentation snapshot: 2026-09-08
Prerequisites
Node.js 22+ PostgreSQL 16+ AWS account with Bedrock access Twilio voice number
Install locally or stage a Droplet
Use INSTALL-WINDOWS.cmd for native Windows or scripts/deploy-digitalocean.ps1 for an isolated shared-Droplet deployment.
Deploy the database
The Droplet bootstrap creates a separate loopback-only PostgreSQL cluster, generates separate owner/runtime credentials, runs migrations, and verifies the restricted application role without changing other clusters. Command: npm run db:ensure && npm run db:deploy
Configure a firm and voice agent
Create the firm through the management API and upload the approved system prompt and called Twilio number.
Point Twilio to the voice service
Set the number's incoming-call webhook to /twilio/incoming. The returned TwiML opens the bidirectional media stream.
Start all services
Run the API, voice service, and durable worker as separate native Node.js services.
Expected result
A signed Twilio call is streamed directly to Nova, the intake is stored once, and the worker archives and delivers it.
Security notes
Do not expose the selected PostgreSQL port or owner credentials to API, voice, worker, or the public internet. Use HTTPS/WSS and validate every Twilio signature.
Validate the incoming webhook
The service verifies X-Twilio-Signature against the exact public webhook URL and submitted form fields.
Resolve configuration by called number
The called Twilio number maps server-side to one active firm and approved prompt.
Detect the caller's language
Nova 2 Sonic detects English or Spanish and uses a multilingual voice. The caller can switch languages during the call.
Stream audio to Nova
Node converts Twilio mu-law audio to 16 kHz PCM and converts Nova output back to Twilio media frames.
Use native tools
Nova calls complete_intake, send_intake_link, dispatch_urgent_alert, transfer_call, and end_call. Node validates and executes each tool. send_intake_link requires the caller's explicitly confirmed number, explicit transactional-SMS consent, and a resolved urgency screen before it will fire.
Persist lifecycle and transcript
Started, alert, completed, and ended events are idempotent and transcript segments are tenant-scoped.
Expected result
No external voice-agent platform is required at runtime.
Twilio receives 401
The exact public URL, auth token, or proxy scheme does not match. Compare the configured public URL with the URL Twilio signs and confirm proxy headers are correct.
Security notes
Never log caller audio, transcript text, credentials, or tool arguments in ordinary service logs.
Create the receiving endpoint
Use an HTTPS endpoint controlled by the product or the law firm.
Verify the signature
Verify the timestamped HMAC signature before processing the exact request body.
Enforce idempotency
Store the stable delivery idempotency key and return success for safe duplicates.
Test retries and DLQ
Cause temporary and permanent failures and confirm retries, visibility timeout, and dead-letter review.
Expected result
Each completed intake reaches the receiver once logically, even when network retries occur.
Security notes
The delivery receiver must not write caller details to unredacted application logs.
Run supervised call scenarios
Test consent, emergencies, silence, interruption, corrections, transfers, out-of-scope callers, and hangups.
Verify records
Compare recordings or supervised notes, transcript, intake fields, consent, alerts, archive, and delivery.
Exercise failure modes
Test Twilio, Nova, database, queue, destination, and service failures without losing or duplicating an intake.
Approve policy and operations
Approve recording, retention, legal hold, privacy, breach, incident, and human-escalation procedures.
Restore from backup
Run the bundled isolated restore test and reconcile known intake records before accepting production traffic.
Expected result
Every blocking launch gate has dated evidence and an accountable owner.
Prerequisites
A firm storage connector holding the firm's RSA-OAEP-256 public key The firm's own S3-compatible bucket INTAKE_WEB_ENABLED and INTAKE_TRANSACTIONAL_SMS_ENABLED, which stay off until the firm's gates pass
Understand the trust boundary before enabling anything
The caller's browser generates a per-asset AES-256-GCM key, encrypts the form answers and attachments, and wraps that key with the firm's RSA public key. Ciphertext is uploaded directly to the firm's bucket using a short-lived presigned URL. ASL stores only receipt metadata: asset identifiers, byte sizes, SHA-256 checksums, and timestamps.
Send an invitation only with explicit consent
An invitation is created for a confirmed mobile number with explicit transactional-SMS consent. The one-time link travels only in the SMS body and only in the URL fragment, so the token is never sent to the server in a request line or written to an access log.
Let the caller complete the form
The link is single-use. Redeeming it exchanges the token for an HttpOnly, Secure, SameSite=Strict session cookie plus a CSRF cookie, and the token is scrubbed from browser history.
Verify the upload before accepting a submission
Every ciphertext object is re-checked in the firm's bucket for size, content type, metadata binding, and checksum before the submission is marked complete. A mismatch fails the submission rather than recording a receipt for content that cannot be trusted.
Retrieve and decrypt on the firm's own systems
Firm staff request short-lived presigned download URLs, then decrypt with the firm's private key, verify the envelope and checksum, and scan for malware in an isolated workspace before promoting anything into their case system.
Expected result
The firm can read its own web-form submissions. ASL can prove a submission happened and cannot read what it contained.
An invitation cannot be created and the API returns firm_storage_not_ready.
The firm's storage connector is missing or is not in active status. Configure the connector and move it to active only after the firm's upload, download, decrypt, checksum, malware-quarantine, and key-recovery exercise has passed.
Updating a firm's bucket or public key returns firm_storage_connector_locked.
Routing and key material become immutable once that firm's first invitation exists, so ciphertext already in the bucket can never be silently orphaned. Follow the documented break-glass rotation procedure. Do not edit the connector row directly.
Security notes
ASL never holds the firm's private key and never holds a readable web-form answer or attachment. Any request to inspect submission content server-side is a design violation, not a debugging shortcut. Losing the firm's private key makes existing ciphertext permanently unrecoverable, which is why two approved encrypted recovery copies are required before a live invitation is sent. Rotating the public key does not re-encrypt existing objects; content encrypted to the old key still needs the old key.