← back to Dw Rotation Activator
Enable verified one-day activation catch-up within daily allowance
9720a3e91e6e77e6059eaa39bbcd747ff174a0c2 · 2026-09-09 11:35:52 -0700 · Steve Abrams
Files touched
A config/daily-catchup-2026-09-09.jsonA lib/activation-allowance.jsM rotate-activate.jsA test/activation-allowance.test.jsA verification/e2e-proof-before-TK11326.jsonM verification/e2e-proof.json
Diff
commit 9720a3e91e6e77e6059eaa39bbcd747ff174a0c2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 11:35:52 2026 -0700
Enable verified one-day activation catch-up within daily allowance
---
config/daily-catchup-2026-09-09.json | 11 ++++
lib/activation-allowance.js | 30 +++++++++
rotate-activate.js | 23 +++++--
test/activation-allowance.test.js | 45 ++++++++++++++
verification/e2e-proof-before-TK11326.json | 82 +++++++++++++++++++++++++
verification/e2e-proof.json | 98 +++++++++++-------------------
6 files changed, 220 insertions(+), 69 deletions(-)
diff --git a/config/daily-catchup-2026-09-09.json b/config/daily-catchup-2026-09-09.json
new file mode 100644
index 0000000..e4a3033
--- /dev/null
+++ b/config/daily-catchup-2026-09-09.json
@@ -0,0 +1,11 @@
+{
+ "schema_version": 1,
+ "ticket": "TK-11326",
+ "date": "2026-09-09",
+ "approved_by": "Steve",
+ "authorization": "start loading for the day now",
+ "starting_daily_used": 37,
+ "max_additional": 462,
+ "max_daily": 500,
+ "scope": "One-time catch-up for this date. Skip hourly pacing only when explicitly invoked; preserve the fixed vendor authorization, all other gates, zero Gemini and daily500."
+}
diff --git a/lib/activation-allowance.js b/lib/activation-allowance.js
new file mode 100644
index 0000000..e8ef87b
--- /dev/null
+++ b/lib/activation-allowance.js
@@ -0,0 +1,30 @@
+'use strict';
+
+function assertDailyCatchup(date, authorization, now = new Date()) {
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date || '') || date !== now.toISOString().slice(0, 10) ||
+ authorization?.schema_version !== 1 || authorization.ticket !== 'TK-11326' ||
+ authorization.date !== date || authorization.approved_by !== 'Steve' ||
+ authorization.authorization !== 'start loading for the day now' ||
+ authorization.starting_daily_used !== 37 || authorization.max_additional !== 462 || authorization.max_daily !== 500)
+ throw new Error('Daily catch-up authorization is invalid or expired');
+}
+
+function activationAllowance({ dailyUsed, hourlyUsed, requested = null, dailyCap = 500, hourlyCap = 21,
+ catchupDate = null, authorization = null, now = new Date() }) {
+ if (![dailyUsed, hourlyUsed].every(n => Number.isSafeInteger(n) && n >= 0) ||
+ !Number.isSafeInteger(dailyCap) || dailyCap < 1 || dailyCap > 500 ||
+ !Number.isSafeInteger(hourlyCap) || hourlyCap < 1 || hourlyCap > 21 ||
+ (requested !== null && (!Number.isSafeInteger(requested) || requested < 1)))
+ throw new Error('Invalid activation limits; refusing an unbounded run');
+ const dailyRemaining = Math.max(0, dailyCap - dailyUsed);
+ const hourlyRemaining = Math.max(0, hourlyCap - hourlyUsed);
+ if (catchupDate !== null) {
+ assertDailyCatchup(catchupDate, authorization, now);
+ if (dailyUsed < authorization.starting_daily_used) throw new Error('Daily ledger is below approved catch-up baseline');
+ const authorizedRemaining = Math.max(0, authorization.starting_daily_used + authorization.max_additional - dailyUsed);
+ return { cap: Math.min(requested ?? dailyRemaining, dailyRemaining, authorizedRemaining), mode: 'daily-catchup', dailyRemaining, hourlyRemaining };
+ }
+ return { cap: Math.min(requested ?? dailyRemaining, dailyRemaining, hourlyRemaining), mode: 'hourly', dailyRemaining, hourlyRemaining };
+}
+
+module.exports = { activationAllowance, assertDailyCatchup };
diff --git a/rotate-activate.js b/rotate-activate.js
index 5d3d33f..a3a4443 100644
--- a/rotate-activate.js
+++ b/rotate-activate.js
@@ -51,6 +51,7 @@ const { canonicalProduct, widthFromLive, completeLiveProduct } = require('./lib/
const { buildRepairReport } = require('./lib/readiness-repairs.js');
const { confirmedActivation } = require('./lib/activation-result.js');
const { countHourlyActivations } = require('./lib/hourly-activation-count.js');
+const { activationAllowance, assertDailyCatchup } = require('./lib/activation-allowance.js');
const { reusedMfrSet, stagingColorFor } = require('./lib/mfr-gate-resolve.js');
// Canonical showroom-vendor primitive (list + logic live in fix-live-board/config). Showroom-only
// vendors are addressable-not-discoverable: never rotate them into the New Arrivals discoverability
@@ -66,6 +67,9 @@ const val = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] :
const COMMIT = flag('--commit');
const DRY = flag('--dry-run') || !COMMIT; // safe-by-default: no --commit ⇒ dry-run
const CLI_MAX = val('--max', null);
+const CATCHUP_DATE = flag('--daily-catchup') ? val('--daily-catchup', '') : null;
+const CATCHUP_AUTHORIZATION = CATCHUP_DATE !== null ? require('./config/daily-catchup-2026-09-09.json') : null;
+if (CATCHUP_DATE !== null) assertDailyCatchup(CATCHUP_DATE, CATCHUP_AUTHORIZATION);
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const API = '2024-10';
@@ -75,6 +79,7 @@ const AUDIT = path.join(OUTDIR, `rotation-activations-${TODAY}.jsonl`);
const REPAIR_REPORT = path.join(OUTDIR, 'current-readiness-repairs.json');
const runDecisions = [];
function recordDecision(record) {
+ if (CATCHUP_DATE !== null) record.pacing_authorization = { mode: 'daily-catchup', ticket: 'TK-11326', date: CATCHUP_DATE };
fs.appendFileSync(AUDIT, JSON.stringify(record) + '\n');
runDecisions.push(record);
}
@@ -279,16 +284,18 @@ function leakGuard(title, vendor) {
if (DRY) {
cap = CLI_MAX != null ? parseInt(CLI_MAX, 10) : 50;
} else {
- const remaining = Math.max(0, DAILY_ACTIVATION_CAP - used);
- const want = CLI_MAX != null ? parseInt(CLI_MAX, 10) : remaining;
const hourlyUsed = countHourlyActivations(fs.existsSync(AUDIT) ? fs.readFileSync(AUDIT, 'utf8') : '');
- const hourlyRemaining = Math.max(0, HOURLY_ACTIVATION_CAP - hourlyUsed);
- cap = Math.min(want, remaining, hourlyRemaining);
+ const allowance = activationAllowance({ dailyUsed: used, hourlyUsed,
+ requested: CLI_MAX !== null ? Number(CLI_MAX) : null, dailyCap: DAILY_ACTIVATION_CAP,
+ hourlyCap: HOURLY_ACTIVATION_CAP, catchupDate: CATCHUP_DATE, authorization: CATCHUP_AUTHORIZATION });
+ cap = allowance.cap;
if (cap <= 0) {
console.log(`[budget] activation allowance reached (daily ${used}/${DAILY_ACTIVATION_CAP}, hourly ${hourlyUsed}/${HOURLY_ACTIVATION_CAP}). Nothing to do this run.`);
return;
}
- console.log(`[budget] hourly activation lane: used ${hourlyUsed}/${HOURLY_ACTIVATION_CAP}; manual and scheduled runs share this allowance.`);
+ console.log(allowance.mode === 'daily-catchup'
+ ? `[budget] one-time daily catch-up: ${CATCHUP_DATE} TK-11326; hourly pacing skipped, daily cap remains ${DAILY_ACTIVATION_CAP}.`
+ : `[budget] hourly activation lane: used ${hourlyUsed}/${HOURLY_ACTIVATION_CAP}; manual and scheduled runs share this allowance.`);
console.log(`[budget] activation lane: used ${used}/${DAILY_ACTIVATION_CAP} today → activating up to ${cap} this run.`);
}
console.log(`mode=${DRY ? 'DRY-RUN' : 'COMMIT'} cap_this_run=${cap}\n`);
@@ -306,6 +313,7 @@ function leakGuard(title, vendor) {
if (r.status !== 200 || r.json?.errors?.length || !Array.isArray(r.json?.data?.nodes))
throw new Error('Shopify product batch read failed; activation stopped');
for (const initialNode of r.json.data.nodes) {
+ if (CATCHUP_DATE !== null) assertDailyCatchup(CATCHUP_DATE, CATCHUP_AUTHORIZATION);
if (activated >= cap) break;
if (!initialNode) continue;
let n = initialNode;
@@ -407,6 +415,11 @@ function leakGuard(title, vendor) {
}
// COMMIT: flip → ACTIVE, add 'New Arrival', publish to channels (ex-Google).
+ if (CATCHUP_DATE !== null) assertDailyCatchup(CATCHUP_DATE, CATCHUP_AUTHORIZATION);
+ const activeCeiling = CATCHUP_DATE !== null
+ ? Math.min(DAILY_ACTIVATION_CAP, CATCHUP_AUTHORIZATION.starting_daily_used + CATCHUP_AUTHORIZATION.max_additional)
+ : DAILY_ACTIVATION_CAP;
+ if (ledgerUsed() >= activeCeiling) throw new Error('Daily activation allowance reached during run');
const ar = await gqlRetry(ACTIVATE, { id: n.id });
const aue = ar.json?.data?.productUpdate?.userErrors;
if (!confirmedActivation(ar, n.id)) {
diff --git a/test/activation-allowance.test.js b/test/activation-allowance.test.js
new file mode 100644
index 0000000..98d7489
--- /dev/null
+++ b/test/activation-allowance.test.js
@@ -0,0 +1,45 @@
+'use strict';
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { activationAllowance, assertDailyCatchup } = require('../lib/activation-allowance');
+const authorization = require('../config/daily-catchup-2026-09-09.json');
+const base = { dailyUsed: 37, hourlyUsed: 21, requested: 463, now: new Date('2026-09-09T18:20:00Z') };
+const catchup = extra => ({ ...base, catchupDate: '2026-09-09', authorization, ...extra });
+test('normal scheduled runs retain the21/hour cap', () => {
+ assert.equal(activationAllowance(base).cap, 0);
+ assert.equal(activationAllowance({ ...base, hourlyUsed: 0 }).cap, 21);
+ assert.equal(activationAllowance({ ...base, hourlyUsed: 20 }).cap, 1);
+});
+test('explicit current-day catch-up can use remaining daily allowance', () => {
+ const result = activationAllowance(catchup());
+ assert.equal(result.cap, 462); assert.equal(result.mode, 'daily-catchup');
+ assert.equal(activationAllowance(catchup({ requested: 1 })).cap, 1);
+});
+test('retries cannot exceed500 total activations', () => {
+ assert.equal(activationAllowance(catchup({ dailyUsed: 498 })).cap, 1);
+ assert.equal(activationAllowance(catchup({ dailyUsed: 499 })).cap, 0);
+ assert.equal(activationAllowance(catchup({ dailyUsed: 500 })).cap, 0);
+ assert.equal(activationAllowance(catchup({ dailyUsed: 501 })).cap, 0);
+});
+test('having an authorization file does not enable catch-up for a scheduled invocation', () => {
+ assert.equal(activationAllowance({ ...base, authorization }).cap, 0);
+});
+test('missing, altered, future and expired authorizations fail closed', () => {
+ for (const change of [{ authorization: null }, { authorization: { ...authorization, max_daily: 501 } },
+ { authorization: { ...authorization, max_additional: 463 } },
+ { authorization: { ...authorization, approved_by: 'someone else' } }, { catchupDate: '2026-09-10' },
+ { now: new Date('2026-09-10T00:00:00Z') }, { now: new Date('2026-09-08T23:59:59Z') }])
+ assert.throws(() => activationAllowance(catchup(change)));
+});
+test('the publication-loop date check stops a run crossing midnight', () => {
+ assert.doesNotThrow(() => assertDailyCatchup('2026-09-09', authorization, new Date('2026-09-09T23:59:59Z')));
+ assert.throws(() => assertDailyCatchup('2026-09-09', authorization, new Date('2026-09-10T00:00:00Z')));
+});
+test('a reset ledger cannot grant the original allowance again', () => {
+ assert.throws(() => activationAllowance(catchup({ dailyUsed: 0 })));
+});
+test('malformed and inflated limits cannot disable either cap', () => {
+ for (const change of [{ requested: NaN }, { requested: -1 }, { requested: 0 }, { requested: Infinity },
+ { dailyCap: 501 }, { hourlyCap: 22 }, { dailyUsed: NaN }, { hourlyUsed: -1 }])
+ assert.throws(() => activationAllowance(catchup(change)));
+});
diff --git a/verification/e2e-proof-before-TK11326.json b/verification/e2e-proof-before-TK11326.json
new file mode 100644
index 0000000..b983a3a
--- /dev/null
+++ b/verification/e2e-proof-before-TK11326.json
@@ -0,0 +1,82 @@
+{
+ "ticket": "TK-11321",
+ "timestamp": "2026-09-09T18:15:15.306027+00:00",
+ "risk_tier": "R4",
+ "authorization": "Steve: skip gemeni and go",
+ "verdict": "PASS: owner-authorized no-Gemini rollout, live batch and hourly cap verified",
+ "runtime": "dw-rotation-activator on live designer-laboratory-sandbox Shopify store",
+ "reviewed_commit": "3d2ba05",
+ "scope": {
+ "captured_outage_held": 504,
+ "historical_blocks_preserved_in_cohort": 21,
+ "eligible_before_fresh_checks": 483,
+ "gemini_requests_permitted": 0
+ },
+ "checks": [
+ {
+ "check": "Installed regression tests",
+ "verdict": "PASS",
+ "tests": 46
+ },
+ {
+ "check": "Live dry run",
+ "verdict": "PASS",
+ "scanned": 287,
+ "would_activate": 21,
+ "blocked": 21,
+ "gemini_calls": 0
+ },
+ {
+ "check": "One-product publication and independent verification",
+ "verdict": "PASS",
+ "product": "DWDX-220551",
+ "ledger_before": 16,
+ "ledger_after": 17,
+ "controls_still_draft": 23,
+ "storefront": [
+ {
+ "url": "https://www.designerwallcoverings.com/products/august-lemongrass-dwdx-220551",
+ "status": 200,
+ "product_id_found": true
+ }
+ ]
+ },
+ {
+ "check": "Exact before-file rollback rehearsal",
+ "verdict": "PASS",
+ "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/rollback-rehearsal.json"
+ },
+ {
+ "check": "Honest skipped-review persistence",
+ "verdict": "PASS",
+ "value": "SKIPPED_BY_OWNER, TK-11321",
+ "review_performed": false
+ },
+ {
+ "check": "Full hourly batch independently verified",
+ "verdict": "PASS",
+ "new_activations": 21,
+ "ledger_before": 16,
+ "ledger_after": 37,
+ "controls_still_draft": 23,
+ "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/live-verified-21.json"
+ },
+ {
+ "check": "Actual scheduled entrypoint stops at21/hour",
+ "verdict": "PASS",
+ "extra_activations": 0,
+ "exit_code": 0,
+ "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/hourly-cap-verified.json"
+ }
+ ],
+ "constraints": "21/hour shared by manual and scheduled runs; 500/day; existing Google exclusion and other readiness/vendor/reintroduction guards retained. No binding-text, AI-generator, billing or credentials changes.",
+ "evidence_directory": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321",
+ "rollback": "Restore exact prefiles under existing job lock; no automatic product rollback.",
+ "residual": "Owner-authorized products have not received automated image-review clearance. Changed identity/image URL or known blocks remain held.",
+ "production_commit": "b03419a",
+ "newly_activated": 21,
+ "daily_ledger": 37,
+ "gemini_requests": 0,
+ "lock_released_at": "2026-09-09T18:12:42.131826+00:00",
+ "schedule_loaded": true
+}
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index b983a3a..d71aa77 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,82 +1,52 @@
{
- "ticket": "TK-11321",
- "timestamp": "2026-09-09T18:15:15.306027+00:00",
+ "ticket": "TK-11326",
+ "timestamp": "2026-09-09T18:35:51.996567+00:00",
"risk_tier": "R4",
- "authorization": "Steve: skip gemeni and go",
- "verdict": "PASS: owner-authorized no-Gemini rollout, live batch and hourly cap verified",
- "runtime": "dw-rotation-activator on live designer-laboratory-sandbox Shopify store",
- "reviewed_commit": "3d2ba05",
- "scope": {
- "captured_outage_held": 504,
- "historical_blocks_preserved_in_cohort": 21,
- "eligible_before_fresh_checks": 483,
- "gemini_requests_permitted": 0
+ "intent": "Load today\u2019s remaining eligible DW drafts now",
+ "authorization": "Steve: start loading for the day now",
+ "verdict": "PASS: installed code and first-product canary; remaining batch pending",
+ "environment": "Production DW Shopify activation pipeline",
+ "reviewed_commit": "7ec14eb",
+ "baseline": {
+ "daily_used": 37,
+ "eligible": 462,
+ "controls": 23
},
+ "commands": [
+ "node --test",
+ "node rotate-activate.js --daily-catchup 2026-09-09 --max 1 --commit",
+ "node scripts/verify-daily-catchup-independent.js after 1"
+ ],
"checks": [
{
"check": "Installed regression tests",
"verdict": "PASS",
- "tests": 46
- },
- {
- "check": "Live dry run",
- "verdict": "PASS",
- "scanned": 287,
- "would_activate": 21,
- "blocked": 21,
- "gemini_calls": 0
- },
- {
- "check": "One-product publication and independent verification",
- "verdict": "PASS",
- "product": "DWDX-220551",
- "ledger_before": 16,
- "ledger_after": 17,
- "controls_still_draft": 23,
- "storefront": [
- {
- "url": "https://www.designerwallcoverings.com/products/august-lemongrass-dwdx-220551",
- "status": 200,
- "product_id_found": true
- }
- ]
- },
- {
- "check": "Exact before-file rollback rehearsal",
- "verdict": "PASS",
- "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/rollback-rehearsal.json"
+ "tests": 54
},
{
- "check": "Honest skipped-review persistence",
+ "check": "Independent live canary, persisted state, publication and public PDP",
"verdict": "PASS",
- "value": "SKIPPED_BY_OWNER, TK-11321",
- "review_performed": false
+ "evidence": "/Users/macstudio3/Projects/dw-daily-catchup-20260909/verification/tk11326/live-verified-1.json"
},
{
- "check": "Full hourly batch independently verified",
+ "check": "Exact-file rollback rehearsal",
"verdict": "PASS",
- "new_activations": 21,
- "ledger_before": 16,
- "ledger_after": 37,
- "controls_still_draft": 23,
- "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/live-verified-21.json"
+ "evidence": "/Users/macstudio3/Projects/dw-daily-catchup-20260909/verification/tk11326/rollback-rehearsal.json"
},
{
- "check": "Actual scheduled entrypoint stops at21/hour",
- "verdict": "PASS",
- "extra_activations": 0,
- "exit_code": 0,
- "evidence": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321/hourly-cap-verified.json"
+ "check": "Remaining daily batch",
+ "verdict": "PENDING"
}
],
- "constraints": "21/hour shared by manual and scheduled runs; 500/day; existing Google exclusion and other readiness/vendor/reintroduction guards retained. No binding-text, AI-generator, billing or credentials changes.",
- "evidence_directory": "/Users/macstudio3/Projects/dw-gemini-skip-TK11321/verification/tk11321",
- "rollback": "Restore exact prefiles under existing job lock; no automatic product rollback.",
- "residual": "Owner-authorized products have not received automated image-review clearance. Changed identity/image URL or known blocks remain held.",
- "production_commit": "b03419a",
- "newly_activated": 21,
- "daily_ledger": 37,
- "gemini_requests": 0,
- "lock_released_at": "2026-09-09T18:12:42.131826+00:00",
- "schedule_loaded": true
+ "constraints": {
+ "daily_cap": 500,
+ "catchup_ceiling": 499,
+ "additional_max": 462,
+ "date": "2026-09-09",
+ "default_hourly_cap": 21,
+ "gemini_requests": 0
+ },
+ "cleanup": "Owned lock retained until batch verification",
+ "rollback": "Source preimages captured and restoration rehearsed; no automatic product rollback",
+ "evidence_directory": "/Users/macstudio3/Projects/dw-daily-catchup-20260909/verification/tk11326"
}
← ea60cf7 Keep released runtime worker locks out of repository snapsho
·
back to Dw Rotation Activator
·
Require confirmed Online Store availability before counting 0add27a →