[object Object]

← back to Dw Marketing Reels

reels: honest TikTok status + FTC content-claims hold guard (pre-arm)

5121ca16073d8d322d70683fc4b877f416c0bd5e · 2026-07-27 16:35:47 -0700 · Steve Abrams

- postTikTok: when armed+token'd but the Content Posting API uploader isn't built,
  return status 'not-implemented' + console.warn instead of a silent 'pending-reconnect'
  that could read as handled on an armed run (contrarian: silent-success bomb).
- Content-claims gate (comms-compliance Fix 1): HOLD any reel whose caption carries an
  unsubstantiated factual claim ('For 25 years', 'As seen in AD/Elle Decor/WSJ') — status
  'held-claims-review' — until CLAIMS_SUBSTANTIATED=1. Verified: holds exactly the 3
  dw-brand-promo reels (15-17), none of the other 30. Fail-safe default = hold.

TK-14. Lets Steve arm the pipeline WITHOUT the unsubstantiated promo reels posting.

Files touched

Diff

commit 5121ca16073d8d322d70683fc4b877f416c0bd5e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 27 16:35:47 2026 -0700

    reels: honest TikTok status + FTC content-claims hold guard (pre-arm)
    
    - postTikTok: when armed+token'd but the Content Posting API uploader isn't built,
      return status 'not-implemented' + console.warn instead of a silent 'pending-reconnect'
      that could read as handled on an armed run (contrarian: silent-success bomb).
    - Content-claims gate (comms-compliance Fix 1): HOLD any reel whose caption carries an
      unsubstantiated factual claim ('For 25 years', 'As seen in AD/Elle Decor/WSJ') — status
      'held-claims-review' — until CLAIMS_SUBSTANTIATED=1. Verified: holds exactly the 3
      dw-brand-promo reels (15-17), none of the other 30. Fail-safe default = hold.
    
    TK-14. Lets Steve arm the pipeline WITHOUT the unsubstantiated promo reels posting.
---
 scripts/publish-social.mjs | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/scripts/publish-social.mjs b/scripts/publish-social.mjs
index 2c46691..b4e1b3d 100644
--- a/scripts/publish-social.mjs
+++ b/scripts/publish-social.mjs
@@ -29,6 +29,14 @@ const IG_AUTH = process.env.IG_AGENT_AUTH || '';
 const PUBLIC_BASE = process.env.PUBLIC_BASE || 'https://marketing.designerwallcoverings.com';
 const now = () => new Date().toISOString();
 
+// Content-claims gate (comms-compliance Fix 1, 2026-07-27): brand-promo reels carry factual
+// advertising claims ("For 25 years…", "As seen in AD/Elle Decor/WSJ") that are FTC §5 exposure
+// until substantiated. Default = HOLD any claim-bearing reel; release only after Steve confirms
+// the claims are true by setting CLAIMS_SUBSTANTIATED=1. Mirrors the fail-safe ARM/leak-gate pattern.
+const CLAIMS_OK = process.env.CLAIMS_SUBSTANTIATED === '1';
+const CLAIM_RE = /\bas seen in\b|\bfor \d+\s*years\b|\bfeatured in\b|architectural digest|elle decor|\bwsj\b|wall street journal/i;
+const claimsHold = reel => !CLAIMS_OK && CLAIM_RE.test([reel.caption, reel.title].filter(Boolean).join('  '));
+
 async function withTimeout(p, ms = 12000) {
   return Promise.race([p, new Promise((_, r) => setTimeout(() => r(new Error('timeout')), ms))]);
 }
@@ -85,7 +93,11 @@ async function postTikTok(reel) {
   // Content Posting API will accept uploads. Until TIKTOK_ACCESS_TOKEN is present, queue.
   if (!process.env.TIKTOK_ACCESS_TOKEN) return { status: 'pending-reconnect', note: 'TikTok channel not connected', at: now() };
   if (!ARMED) return { status: 'ready-pending-arm', note: 'TikTok token present; set SOCIAL_LIVE_ARMED=1 to enable the first live post', at: now() };
-  return { status: 'pending-reconnect', note: 'connected-token present but uploader wiring is a follow-up', at: now() };
+  // Armed + token present, but the Content Posting API uploader is NOT built yet. Fail LOUD
+  // with an honest status instead of a silent 'pending-reconnect' that could read as "handled"
+  // on an armed run — TikTok must not masquerade as live until the uploader ships (contrarian find).
+  console.warn('[postTikTok] ARMED + TIKTOK_ACCESS_TOKEN present but the Content Posting API uploader is NOT implemented — no TikTok post made. Build the uploader before treating TikTok as live.');
+  return { status: 'not-implemented', note: 'armed+token present but Content Posting API uploader is not built yet — no post made', at: now() };
 }
 
 async function main() {
@@ -100,6 +112,17 @@ async function main() {
   }
 
   reel.publish = reel.publish || {};
+
+  // Content-claims HOLD (Fix 1): never auto-post a reel with an unsubstantiated factual claim.
+  if (claimsHold(reel)) {
+    const held = { status: 'held-claims-review', note: 'unsubstantiated advertising claim (FTC §5) — set CLAIMS_SUBSTANTIATED=1 after Steve confirms the claim is true', at: now() };
+    if (CHANNELS.includes('instagram')) reel.publish.instagram = { ...held };
+    if (CHANNELS.includes('tiktok'))    reel.publish.tiktok    = { ...held };
+    writeFileSync(MAN, JSON.stringify(reels, null, 2));
+    console.log(`HELD ${reel.file}: unsubstantiated claim in caption — not posting until CLAIMS_SUBSTANTIATED=1`);
+    return;
+  }
+
   // Duplicate-post guard (comms-compliance find, 2026-07-27): main() always takes reels[0].
   // If a nightly build fails to prepend a new reel but this job still runs, an already-'posted'
   // reel would be resubmitted to the Graph API — a duplicate public post (Meta prohibits

← 19e8ca4 reels: consolidate LEAK_DENY to one canonical-synced module  ·  back to Dw Marketing Reels  ·  reels: fix IG async-publish timeout (first live post landed 8adb693 →