← 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
M scripts/publish-social.mjs
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 →