← back to Norma
TK-11383: enforce the enrollment hold at the PUBLISHER too, not just the builder
3bdc7a564c58a8dfc6b103add85654eccea74091 · 2026-09-13 14:54:29 -0700 · Steve Abrams
Follow-up to 123570c after a second-model review (Kimi) flagged that a hold
enforced only in build-registry.js is defeated by the very things that bypass
the builder: a hand-edited accounts.json, an `--include-held` build that gets
committed, or an older checkout. The irreversible action — a public post —
happens in daily-cadence.js, which posts to every key of accounts.json. So the
publisher now enforces the SAME enrollment-hold.json independently, right where
the harm would occur: any held handle that somehow reached the registry is
dropped from HANDLES before selection and cannot post.
Failure posture mirrors the builder for consistency: a corrupt hold file fails
CLOSED (aborts the whole run, exit 1 — an untrustworthy denylist must not read
as "no holds"); an absent file warns loudly and continues (documented fresh
state; the written registry already excludes held handles). Absent is not
fleet-halting because halting all 35 accounts over one missing config file is
disproportionate, and the file is committed.
Tested read-only (--status, never posts): normal run unchanged and emits no
false HOLD line; a corrupted enrollment-hold.json makes the real cadence abort
exit 1; and a synthetic registry containing @designerschat has it dropped with
a loud HOLD ENFORCED line before any posting logic.
Undo: git revert HEAD (1 file, no runtime state touched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M85r8WQP2nf5jssn8RiuDA
Files touched
M agents/instagram-agent/daily-cadence.js
Diff
commit 3bdc7a564c58a8dfc6b103add85654eccea74091
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Sep 13 14:54:29 2026 -0700
TK-11383: enforce the enrollment hold at the PUBLISHER too, not just the builder
Follow-up to 123570c after a second-model review (Kimi) flagged that a hold
enforced only in build-registry.js is defeated by the very things that bypass
the builder: a hand-edited accounts.json, an `--include-held` build that gets
committed, or an older checkout. The irreversible action — a public post —
happens in daily-cadence.js, which posts to every key of accounts.json. So the
publisher now enforces the SAME enrollment-hold.json independently, right where
the harm would occur: any held handle that somehow reached the registry is
dropped from HANDLES before selection and cannot post.
Failure posture mirrors the builder for consistency: a corrupt hold file fails
CLOSED (aborts the whole run, exit 1 — an untrustworthy denylist must not read
as "no holds"); an absent file warns loudly and continues (documented fresh
state; the written registry already excludes held handles). Absent is not
fleet-halting because halting all 35 accounts over one missing config file is
disproportionate, and the file is committed.
Tested read-only (--status, never posts): normal run unchanged and emits no
false HOLD line; a corrupted enrollment-hold.json makes the real cadence abort
exit 1; and a synthetic registry containing @designerschat has it dropped with
a loud HOLD ENFORCED line before any posting logic.
Undo: git revert HEAD (1 file, no runtime state touched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M85r8WQP2nf5jssn8RiuDA
---
agents/instagram-agent/daily-cadence.js | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/agents/instagram-agent/daily-cadence.js b/agents/instagram-agent/daily-cadence.js
index f7b4db5..960126b 100644
--- a/agents/instagram-agent/daily-cadence.js
+++ b/agents/instagram-agent/daily-cadence.js
@@ -31,6 +31,36 @@ const themes = JSON.parse(fs.readFileSync(path.join(HERE, 'account-themes.json')
const content = require('./content');
const HANDLES = Object.keys(reg);
+
+// ---- Publisher-side enrollment HOLD enforcement (TK-11383) ------------------
+// This is the point of the IRREVERSIBLE action: whatever is in HANDLES gets
+// posted PUBLICLY. build-registry.js honours enrollment-hold.json when it WRITES
+// accounts.json, but that guard is defeated by a hand-edit, an --include-held
+// build that gets committed, or an older checkout. So the publisher enforces the
+// SAME hold list independently, right where the harm would happen. A held handle
+// that somehow reached accounts.json is dropped here and cannot post.
+// Corrupt hold file => fail CLOSED (abort the whole run): an untrustworthy
+// denylist must not be treated as empty. Absent file => warn loudly + continue
+// (documented fresh state; the written registry already excludes held handles).
+(() => {
+ let hold = {};
+ try {
+ hold = JSON.parse(fs.readFileSync(path.join(HERE, 'enrollment-hold.json'), 'utf8')).hold || {};
+ } catch (e) {
+ if (e.code !== 'ENOENT') {
+ console.error(`ABORT: enrollment-hold.json is unreadable (${e.message}). Refusing to post — an untrustworthy hold list must not be read as "no holds".`);
+ process.exit(1);
+ }
+ console.error('WARNING: enrollment-hold.json not present; proceeding with no enrollment holds.');
+ }
+ const heldPresent = Object.keys(hold).filter((h) => Object.prototype.hasOwnProperty.call(reg, h));
+ for (const h of heldPresent) {
+ delete reg[h];
+ const i = HANDLES.indexOf(h);
+ if (i >= 0) HANDLES.splice(i, 1);
+ console.error(`HOLD ENFORCED: @${h} is on the enrollment hold list (${hold[h].ticket || 'no ticket'}) but was present in accounts.json — dropped from this run so it will NOT post publicly. ${hold[h].reason || ''}`);
+ }
+})();
const now = new Date();
const isRestDay = now.getDay() === 0; // Sunday
const today = now.toISOString().slice(0, 10);
← 72f0976 TK-11383: enforce the designerschat enrollment hold at the P
·
back to Norma
·
TK-11383: close the allowlist-expansion path to unapproved p 36f4ede →