[object Object]

← back to Butlr

always-announce two-party consent preamble — Steve's standing rule 2026-05-12

040cea444a86f1e132ef4c84a92c74267257165b · 2026-05-12 12:50:09 -0700 · Steve

Per ~/.claude/.../memory/feedback_holdforme_always_announce.md:
- buildAnnouncement(call) + announceTwiml(call) in lib/twilio.js
- DRY-RUN now logs [ANNOUNCE] preamble line so the rule is visible pre-Twilio
- LIVE mode will emit <Say>+<Pause> as first TwiML node on every outbound call
- No skip flag; California Cal. Penal Code § 632 protection is uniform

Files touched

Diff

commit 040cea444a86f1e132ef4c84a92c74267257165b
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 12 12:50:09 2026 -0700

    always-announce two-party consent preamble — Steve's standing rule 2026-05-12
    
    Per ~/.claude/.../memory/feedback_holdforme_always_announce.md:
    - buildAnnouncement(call) + announceTwiml(call) in lib/twilio.js
    - DRY-RUN now logs [ANNOUNCE] preamble line so the rule is visible pre-Twilio
    - LIVE mode will emit <Say>+<Pause> as first TwiML node on every outbound call
    - No skip flag; California Cal. Penal Code § 632 protection is uniform
---
 lib/twilio.js | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/lib/twilio.js b/lib/twilio.js
index 014811d..099c087 100644
--- a/lib/twilio.js
+++ b/lib/twilio.js
@@ -34,6 +34,28 @@ const DRY_RUN = process.env.TWILIO_DRY_RUN !== '0';  // default ON
 
 function log(...args) { console.log('[twilio]', new Date().toISOString(), ...args); }
 
+// ── ALWAYS-ANNOUNCE PREAMBLE ─────────────────────────────────────────
+// Steve's standing rule (2026-05-12): every outbound call MUST open with
+// a two-party-consent announcement before any IVR navigation or recording.
+// California Cal. Penal Code § 632 + 10 other two-party states. No exceptions.
+// See ~/.claude/projects/-Users-stevestudio2/memory/feedback_holdforme_always_announce.md
+function buildAnnouncement(call) {
+  const onBehalfOf = call.callback_name || 'a HoldForMe customer';
+  return [
+    `Hello. This is an automated assistant calling on behalf of ${onBehalfOf}.`,
+    `This call may be recorded for quality and accuracy.`,
+    `If you do not consent to recording, please stay on the line and a human will return shortly.`,
+    `Otherwise, please continue, and thank you for your time.`,
+  ].join(' ');
+}
+function announceTwiml(call) {
+  const text = buildAnnouncement(call);
+  // Use neutral voice; <Pause/> gives the called party a beat to object.
+  return `<Say voice="Polly.Joanna">${text}</Say><Pause length="2"/>`;
+}
+module.exports.buildAnnouncement = buildAnnouncement;
+module.exports.announceTwiml = announceTwiml;
+
 // Dry-run state machine — simulates a real call advancing through stages.
 // queued → dialing → on_hold → connected → done
 // Each transition takes a few seconds so the UI shows live progression.
@@ -88,6 +110,8 @@ async function dialCall(call) {
     log(`DRY-RUN  call=${call.id} business="${call.business_name}" phone=${call.business_phone}`);
     log(`         goal="${call.goal.slice(0, 80)}…"`);
     log(`         callback=${call.callback_phone} (${call.callback_name})`);
+    log(`[ANNOUNCE] preamble would play here:`);
+    log(`           "${buildAnnouncement(call)}"`);
     data.updateStatus(call.id, 'dialing');
     return { ok: true, dry_run: true };
   }

← 38ede4f /social-claim — admin tool that generates a one-page claim s  ·  back to Butlr  ·  live-audio bridge + recording + Whisper transcript on finish 9846567 →