From df7f9a4807c4585a674a78fcd4a546fbce5aea9a Mon Sep 17 00:00:00 2001 From: Nish <257724087+nish3451@users.noreply.github.com> Date: Tue, 11 Aug 2026 08:06:02 +0530 Subject: [PATCH] fix(sales): block retired offer copy in outbound send packages The active prospect send package embeds recording-notes.md verbatim, which can still carry the retired 7-Day Site Revenue Fault Sprint / 30-day action plan ask. check-outbound-send-readiness only checked opt-out language and placeholders, so a stale recording-notes.md or send-package.md sold the retired offer without any CI failure. Add the canonical retired-offer pattern to the outbound send readiness gate for send-package.md and recording-notes.md, and extend the active-offer projection test to assert the generated send package projects only the canonical founder-pilot offer. Co-authored-by: CommandCodeBot --- scripts/check-outbound-send-readiness.mjs | 9 ++++++++- scripts/test-active-offer-projection.mjs | 10 ++++++++++ scripts/test-outbound-send-readiness.mjs | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/check-outbound-send-readiness.mjs b/scripts/check-outbound-send-readiness.mjs index 57160a08..82864977 100644 --- a/scripts/check-outbound-send-readiness.mjs +++ b/scripts/check-outbound-send-readiness.mjs @@ -76,10 +76,13 @@ try { } const { roots, strict } = config; -const outboundFiles = new Set(["next-message.md", "send-package.md", "outreach.md", "reply-package.md", "call-booked-package.md", "close-package.md"]); +const outboundFiles = new Set(["next-message.md", "send-package.md", "recording-notes.md", "outreach.md", "reply-package.md", "call-booked-package.md", "close-package.md"]); const optOutPattern = /\b(reply no|do not follow up|unsubscribe|opt out|ignore me)\b/i; const placeholderPattern = /\[(?:add Loom link|link|specific fault|Name)\]|Here is the Loom:\s*$/i; const salesPlaceholderPattern = /\badd (?:meeting link|payment link|call time)\b/i; +// Retired broad-agency offers must never project into an outbound send package. +// Mirrors the canonical retired-ask guard in export-recording-rehearsal-check.mjs. +const retiredOfferPattern = /7[-\s]day (?:site|website) revenue (?:leak|fault) (?:fix )?sprint|tangible revenue (?:leak|fault) sprint|30[-\s]day action plan|growth desk|three pages|\$\s?500\b/i; function walk(path) { if (!existsSync(path)) return []; @@ -124,6 +127,10 @@ for (const file of files) { findings.push({ file, rule: "sales package still has meeting/payment placeholders" }); } + if (["send-package.md", "recording-notes.md"].includes(filename) && retiredOfferPattern.test(content)) { + findings.push({ file, rule: "outbound package sells a retired offer" }); + } + if (filename === "outreach.md" && !optOutPattern.test(content)) { warnings.push({ file, rule: "template outreach lacks opt-out language" }); } diff --git a/scripts/test-active-offer-projection.mjs b/scripts/test-active-offer-projection.mjs index 205bca90..41f779ed 100644 --- a/scripts/test-active-offer-projection.mjs +++ b/scripts/test-active-offer-projection.mjs @@ -92,6 +92,16 @@ If useful, I can run a 7-Day Site Revenue Fault Sprint with a 30-day action plan } dnm(loomPackage, retiredOfferPattern) + const sendResult = spawnSync(process.execPath, [sp("prepare-prospect-send.mjs"), legacyProspectPath, "https://www.loom.com/share/1234567890abcdef1234567890abcdef", "--approved", "--force"], {cwd: repositoryRoot, encoding: "utf8"}) + eq(sendResult.status, 0, `send prep failed: ${sendResult.stderr}`) + const sendPackage = readFileSync(join(legacyProspectPath, "send-package.md"), "utf8") + mat(sendPackage, /The Website Correction/) + mat(sendPackage, /one highest-leverage page/) + for (const forbiddenOutcome of NO_GUARANTEE_OUTCOMES) { + mat(sendPackage, new RegExp(forbiddenOutcome.replace("-", "[- ]"), "i")) + } + dnm(sendPackage, retiredOfferPattern) + console.log("Active offer projection checks passed.") } finally { rmSync(testRoot, {recursive: true, force: true}) diff --git a/scripts/test-outbound-send-readiness.mjs b/scripts/test-outbound-send-readiness.mjs index 87565db2..21dbd85d 100644 --- a/scripts/test-outbound-send-readiness.mjs +++ b/scripts/test-outbound-send-readiness.mjs @@ -48,6 +48,23 @@ try { assert.notEqual(result.status, 0) assert(output(result).findings.some(finding => finding.rule === "send package still has placeholders")) + // The send package must never sell a retired broad-agency offer, and + // recording-notes.md is embedded verbatim into the send package. + for (const [file, content] of [ + ["send-package.md", `${packageHeader}7-Day Site Revenue Fault Sprint with a 30-day action plan. ${optOut}`], + ["recording-notes.md", `# Recording Notes\n\n## Quality Notes\n\n- Clean ask: If useful, I can run a Tangible Revenue Fault Sprint with a 30-day action plan for $500.\n`] + ]) { + writeFileSync(join(prospect, file), content) + result = run() + assert.notEqual(result.status, 0, `expected retired-offer failure for ${file}`) + assert(output(result).findings.some(finding => finding.rule === "outbound package sells a retired offer")) + } + + writeFileSync(join(prospect, "send-package.md"), `${packageHeader}The Website Correction, one highest-leverage page, 14-day implementation tracking. ${optOut}`) + writeFileSync(join(prospect, "recording-notes.md"), `# Recording Notes\n\n## Quality Notes\n\n- Clean ask: If useful, I can run a human-reviewed The Website Correction on this one highest-leverage page with a measurement plan.\n`) + result = run() + assert.equal(result.status, 0, "canonical send package must not trigger the retired-offer rule") + result = run(["--roots=alternate"]) assert.equal(result.status, 0) assert.equal(output(result).filesScanned, 1)