[object Object]

← back to CelebritySignatures

add gated Printify fulfillment path for wear

4794797e5836ae223e93a87ea217922ed21725ce · 2026-09-03 13:55:41 -0700 · Steve Abrams

Files touched

Diff

commit 4794797e5836ae223e93a87ea217922ed21725ce
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 3 13:55:41 2026 -0700

    add gated Printify fulfillment path for wear
---
 data/wear-templates.json              | 14 ++++++
 scripts/fetch-printify-catalog.mjs    | 63 ++++++++++++++++++++++++
 scripts/submit-pod-draft-printify.mjs | 92 +++++++++++++++++++++++++++++++++++
 scripts/submit-pod-draft.mjs          |  6 +--
 server.js                             | 31 +++++++++---
 test/printify-pod.test.mjs            | 33 +++++++++++++
 verification/e2e-proof-printify.json  | 46 ++++++++++++++++++
 7 files changed, 276 insertions(+), 9 deletions(-)

diff --git a/data/wear-templates.json b/data/wear-templates.json
index 84f9dd2..fe2b86c 100644
--- a/data/wear-templates.json
+++ b/data/wear-templates.json
@@ -64,6 +64,13 @@
         "placementType": "front",
         "variants": {}
       },
+      "printify": {
+        "_note": "Printify dynamic-order mapping (fill after PRINTIFY_API_TOKEN lands — run scripts/fetch-printify-catalog.mjs). blueprintId + printProviderId select the blank garment; variants maps our color/size key to its variant_id. The paid order's signature image is supplied per order with left-chest positioning. Empty mappings fail loud and keep checkout disabled.",
+        "blueprintId": null,
+        "printProviderId": null,
+        "printPosition": { "placeholder": "front", "scale": 0.25, "x": 0.32, "y": 0.3 },
+        "variants": {}
+      },
       "sizeUpchargeUsd": {
         "2XL": 4
       }
@@ -135,6 +142,13 @@
         "placementType": "front",
         "variants": {}
       },
+      "printify": {
+        "_note": "Printify dynamic-order mapping (fill after PRINTIFY_API_TOKEN lands — run scripts/fetch-printify-catalog.mjs). blueprintId + printProviderId select the blank garment; variants maps our color/size key to its variant_id. The paid order's signature image is supplied per order with left-chest positioning. Empty mappings fail loud and keep checkout disabled.",
+        "blueprintId": null,
+        "printProviderId": null,
+        "printPosition": { "placeholder": "front", "scale": 0.25, "x": 0.32, "y": 0.3 },
+        "variants": {}
+      },
       "sizeUpchargeUsd": {
         "2XL": 4
       }
diff --git a/scripts/fetch-printify-catalog.mjs b/scripts/fetch-printify-catalog.mjs
new file mode 100644
index 0000000..a2fcca8
--- /dev/null
+++ b/scripts/fetch-printify-catalog.mjs
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+// READ-ONLY Printify account/catalog helper for TK-10286.
+// Lists the account's shops, blank-product blueprints, print providers, and
+// variants. It never creates products, orders, or production jobs.
+import { fileURLToPath } from 'node:url';
+import { join } from 'node:path';
+import { readFileSync } from 'node:fs';
+
+const ROOT = fileURLToPath(new URL('..', import.meta.url));
+const API = 'https://api.printify.com/v1';
+function envVal(name) {
+  if (process.env[name]) return process.env[name];
+  for (const p of [join(ROOT, '.env'), join(ROOT, '..', 'secrets-manager', '.env')]) {
+    try { const m = readFileSync(p, 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
+  }
+  return null;
+}
+async function get(token, path) {
+  const r = await fetch(API + path, { headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'CelebritySignatures/1.0' } });
+  const j = await r.json().catch(() => ({}));
+  if (!r.ok) throw new Error(`Printify ${r.status}: ${j.message || j.error || 'error'} on ${path}`);
+  return j;
+}
+async function main() {
+  const token = envVal('PRINTIFY_API_TOKEN');
+  if (!token) {
+    console.log('PRINTIFY_API_TOKEN not set — generate a scoped personal access token in Printify Profile > Connections, then paste it via the secrets skill.');
+    console.log('This helper is GET-only and will discover shops/products after the token is routed.');
+    return;
+  }
+  const shops = await get(token, '/shops.json');
+  const blueprintArg = process.argv[2];
+  if (!blueprintArg) {
+    console.log(`Printify shops (${shops.length}):`);
+    for (const shop of shops) console.log(`  id=${shop.id}  ${shop.title}  (${shop.sales_channel || 'unknown channel'})`);
+    const blueprints = await get(token, '/catalog/blueprints.json');
+    const candidates = blueprints.filter(b => /t-?shirt|tee|polo/i.test(b.title || ''));
+    console.log(`\nBlank garment blueprints matching tee/polo (${candidates.length}):`);
+    for (const b of candidates.slice(0, 50)) console.log(`  id=${b.id}  ${b.title}`);
+    console.log('\nNext: node scripts/fetch-printify-catalog.mjs <blueprintId> [printProviderId]');
+    return;
+  }
+  const providerArg = process.argv[3];
+  if (!providerArg) {
+    const providers = await get(token, `/catalog/blueprints/${blueprintArg}/print_providers.json`);
+    console.log(`Print providers for blueprint ${blueprintArg} (${providers.length}):`);
+    for (const p of providers) console.log(`  id=${p.id}  ${p.title}`);
+    console.log(`\nNext: node scripts/fetch-printify-catalog.mjs ${blueprintArg} <printProviderId>`);
+    return;
+  }
+  const catalog = await get(token, `/catalog/blueprints/${blueprintArg}/print_providers/${providerArg}/variants.json`);
+  const variants = {};
+  for (const v of catalog.variants || []) {
+    const title = String(v.title || '');
+    const size = title.match(/(?:^|\s\/\s)(S|M|L|XL|2XL)(?:$|\s\/\s)/i)?.[1]?.toUpperCase();
+    const color = title.split(/\s\/\s/).find(x => !/^(S|M|L|XL|2XL)$/i.test(x));
+    if (color && size) variants[`${color}/${size}`] = v.id;
+  }
+  console.log(JSON.stringify({ blueprintId: Number(blueprintArg), printProviderId: Number(providerArg),
+    printPosition: { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 }, variants }, null, 2));
+  console.log('\nNormalize Printify color labels to our white/heather/sand/sky ids before saving the mapping.');
+}
+main().catch(e => { console.error(e.message); process.exit(1); });
diff --git a/scripts/submit-pod-draft-printify.mjs b/scripts/submit-pod-draft-printify.mjs
new file mode 100644
index 0000000..bfc76f6
--- /dev/null
+++ b/scripts/submit-pod-draft-printify.mjs
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+// Steve-gated Printify order sender for TK-10286. Printify order creation can
+// enter production depending on account settings, so this refuses to POST unless
+// all FIVE gates are explicit: --apply, token, shop id, live sales, and a manual
+// approval acknowledgement. A provider-specific marker prevents duplicate sends.
+import { readFile, appendFile } from 'node:fs/promises';
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { join } from 'node:path';
+import { pathToFileURL } from 'node:url';
+
+const ROOT = fileURLToPath(new URL('..', import.meta.url));
+const DATA = join(ROOT, 'data');
+const API = 'https://api.printify.com/v1';
+const APPLY = process.argv.includes('--apply');
+function envVal(name) {
+  if (process.env[name]) return process.env[name];
+  for (const p of [join(ROOT, '.env'), join(ROOT, '..', 'secrets-manager', '.env')]) {
+    try { const m = readFileSync(p, 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
+  }
+  return null;
+}
+function resolveProduct(tpl, d) {
+  const garment = (tpl.garments || []).find(g => g.id === d.garment);
+  if (!garment) throw new Error(`order #${d.orderId}: unknown garment "${d.garment}"`);
+  const map = garment.printify || {};
+  const key = `${d.color}/${d.size}`;
+  if (!map.blueprintId || !map.printProviderId || !map.variants?.[key]) throw new Error(`order #${d.orderId}: ${d.garment} ${key} is not mapped to a Printify blueprint/provider/variant`);
+  return { blueprintId: map.blueprintId, printProviderId: map.printProviderId, variantId: map.variants[key],
+    position: map.printPosition || { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 } };
+}
+function splitName(name) {
+  const parts = String(name || '').trim().split(/\s+/).filter(Boolean);
+  return { first_name: parts.shift() || '', last_name: parts.join(' ') || '-' };
+}
+export function buildPayload(tpl, d) {
+  const { blueprintId, printProviderId, variantId, position } = resolveProduct(tpl, d);
+  const r = d.recipient || {};
+  const missing = ['name', 'address1', 'city', 'state_code', 'country_code', 'zip'].filter(k => !r[k]);
+  if (missing.length) throw new Error(`order #${d.orderId}: missing shipping fields ${missing.join(',')}`);
+  if (!d.design_image_url) throw new Error(`order #${d.orderId}: no design_image_url`);
+  const name = splitName(r.name);
+  return {
+    external_id: `celebsig-wear-${d.orderId}`,
+    label: `${d.signature_name} signature — left-chest ${d.garment}`,
+    line_items: [{ print_provider_id: printProviderId, blueprint_id: blueprintId, variant_id: variantId, quantity: 1,
+      external_id: `celebsig-wear-${d.orderId}-1`,
+      print_areas: { [position.placeholder || 'front']: [{ src: d.design_image_url, scale: position.scale,
+        x: position.x, y: position.y, angle: 0 }] } }],
+    is_printify_express: false,
+    is_economy_shipping: false,
+    shipping_method: 1,
+    send_shipping_notification: false,
+    address_to: { ...name, email: d.recipient_email, phone: '', country: r.country_code, region: r.state_code,
+      address1: r.address1, address2: r.address2 || '', city: r.city, zip: r.zip },
+  };
+}
+async function main() {
+  let lines = [];
+  try { lines = (await readFile(join(DATA, 'pod-order-drafts.jsonl'), 'utf8')).trim().split('\n').filter(Boolean); } catch {}
+  const all = lines.map(JSON.parse);
+  const submitted = new Set(all.filter(x => x.status === 'SUBMITTED' && x.provider === 'printify').map(x => x.orderId));
+  const drafts = all.filter(x => x.status === 'DRAFT_UNSENT' && x.provider === 'printify' && !submitted.has(x.orderId));
+  const tpl = JSON.parse(await readFile(join(DATA, 'wear-templates.json'), 'utf8'));
+  const token = envVal('PRINTIFY_API_TOKEN');
+  const shopId = envVal('PRINTIFY_SHOP_ID');
+  const salesLive = envVal('WEAR_SALES_LIVE') === '1';
+  const manual = envVal('PRINTIFY_MANUAL_APPROVAL_CONFIRMED') === '1';
+  const willApply = APPLY && !!token && !!shopId && salesLive && manual;
+  console.log(`POD drafts pending: ${drafts.length} (provider: Printify)`);
+  for (const d of drafts) {
+    let payload;
+    try { payload = buildPayload(tpl, d); } catch (e) { console.log(`  SKIP: ${e.message}`); continue; }
+    console.log(JSON.stringify(payload, null, 2));
+    if (!willApply) continue;
+    const resp = await fetch(`${API}/shops/${shopId}/orders.json`, { method: 'POST',
+      headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'CelebritySignatures/1.0', 'Content-Type': 'application/json' },
+      body: JSON.stringify(payload) });
+    const body = await resp.json().catch(() => ({}));
+    if (!resp.ok) { console.error(`PRINTIFY ERROR ${resp.status}: ${body.message || body.error || 'unknown'}`); continue; }
+    await appendFile(join(DATA, 'pod-order-drafts.jsonl'), JSON.stringify({ orderId: d.orderId, status: 'SUBMITTED', provider: 'printify', printifyId: body.id, submittedAt: new Date().toISOString() }) + '\n');
+    console.log(`  Printify order created: ${body.id}; production remains controlled by the account's manual approval setting.`);
+  }
+  if (!willApply) {
+    console.log('\nDRY RUN — nothing submitted to Printify.');
+    console.log(`  --apply: ${APPLY} | token: ${!!token} | shop: ${!!shopId} | sales live: ${salesLive} | manual approval confirmed: ${manual}`);
+    console.log('  All five gates must be true. Verify the Printify store Order approval setting is Manual before setting the acknowledgement.');
+  }
+}
+if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
+  main().catch(e => { console.error(e.message); process.exit(1); });
+}
diff --git a/scripts/submit-pod-draft.mjs b/scripts/submit-pod-draft.mjs
index e22faf6..8bdf75c 100644
--- a/scripts/submit-pod-draft.mjs
+++ b/scripts/submit-pod-draft.mjs
@@ -76,8 +76,8 @@ async function main() {
   const all = lines.map(l => JSON.parse(l));
   // Orders already sent to Printful get a SUBMITTED marker line appended below.
   // Exclude them so a rerun (network glitch, human re-run) NEVER double-submits.
-  const submitted = new Set(all.filter(d => d.status === 'SUBMITTED').map(d => d.orderId));
-  const drafts = all.filter(d => d.status === 'DRAFT_UNSENT' && !submitted.has(d.orderId));
+  const submitted = new Set(all.filter(d => d.status === 'SUBMITTED' && (!d.provider || d.provider === 'printful')).map(d => d.orderId));
+  const drafts = all.filter(d => d.status === 'DRAFT_UNSENT' && (!d.provider || d.provider === 'printful') && !submitted.has(d.orderId));
   const tpl = await templates();
   console.log(`POD drafts pending: ${drafts.length} (provider: Printful)`);
 
@@ -101,7 +101,7 @@ async function main() {
     const j = await resp.json().catch(() => ({}));
     if (!resp.ok) { console.error(`  PRINTFUL ERROR ${resp.status}: ${j.error?.message || j.result || 'unknown'}`); continue; }
     // Mark this order SUBMITTED so a rerun skips it (append-only, matches the draft log).
-    await appendFile(join(DATA, 'pod-order-drafts.jsonl'), JSON.stringify({ orderId: d.orderId, status: 'SUBMITTED', printfulId: j.result?.id, submittedAt: new Date().toISOString() }) + '\n');
+    await appendFile(join(DATA, 'pod-order-drafts.jsonl'), JSON.stringify({ orderId: d.orderId, status: 'SUBMITTED', provider: 'printful', printfulId: j.result?.id, submittedAt: new Date().toISOString() }) + '\n');
     console.log(`  ✓ Printful DRAFT order created: id=${j.result?.id} status=${j.result?.status} (confirm it in the Printful dashboard to fulfill).`);
   }
 
diff --git a/server.js b/server.js
index fcaa47f..202f51d 100644
--- a/server.js
+++ b/server.js
@@ -153,6 +153,21 @@ async function wearTemplates() {
   _wearTplAt = Date.now();
   return _wearTplCache;
 }
+const WEAR_POD_PROVIDERS = new Set(['printful', 'printify']);
+function wearPodProvider() {
+  const provider = String(envVal('WEAR_POD_PROVIDER') || 'printful').toLowerCase();
+  return WEAR_POD_PROVIDERS.has(provider) ? provider : null;
+}
+function wearPodReady(tpl, provider) {
+  if (!provider) return false;
+  const credential = provider === 'printify' ? envVal('PRINTIFY_API_TOKEN') : envVal('PRINTFUL_API_KEY');
+  if (!credential) return false;
+  return (tpl.garments || []).every(g => {
+    const mapping = g[provider] || {};
+    if (provider === 'printify' && (!mapping.blueprintId || !mapping.printProviderId)) return false;
+    return (g.colors || []).every(c => (g.sizes || []).every(size => mapping.variants?.[`${c.id}/${size}`]));
+  });
+}
 // v1 (2026-08-10, Steve GO — TK-10286): the USPTO live-trademark auto-screen is DEFERRED.
 // A signature is sellable on apparel only if it is a clearly PUBLIC-DOMAIN HISTORICAL figure
 // — deceased on/before WEAR_PD_DEATH_CUTOFF — where there is no live apparel-trademark or
@@ -791,6 +806,11 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
       // flips WEAR_SALES_LIVE=1 (paired with live Stripe + a wired POD). Until then, no visitor
       // is sent to a test-mode checkout — they get a friendly "opening soon".
       if (envVal('WEAR_SALES_LIVE') !== '1') return sendJSON(res, 200, { ok: false, comingSoon: true, error: 'Apparel sales open soon — thanks for your interest!' });
+      // Never charge a customer unless the selected POD provider has both a
+      // credential and a complete color/size mapping for every offered garment.
+      const podProvider = wearPodProvider();
+      const podTemplates = await wearTemplates();
+      if (!wearPodReady(podTemplates, podProvider)) return sendJSON(res, 503, { ok: false, error: 'apparel fulfillment is not configured' });
       // Apparel is a physical POD sale — charge through the live-capable resolver
       // (STRIPE_MURAL_KEY: live only when STRIPE_LIVE_ENABLED=1 AND a sk_live_ key
       // is present, else the test key). NOT STRIPE_DOWNLOAD_KEY, which is test-only,
@@ -802,7 +822,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
       const clr = await tmClearance();
       const sig = (await mergedSignatures()).find(s => qidOf(s) === String(b.qid || ''));
       if (!sig || !wearEligible(sig, clr)) return sendJSON(res, 403, { ok: false, error: 'that signature is not cleared for sale' });
-      const tpl = await wearTemplates();
+      const tpl = podTemplates;
       const garment = (tpl.garments || []).find(g => g.id === String(b.garment || ''));
       if (!garment) return sendJSON(res, 400, { ok: false, error: 'unknown garment' });
       const color = (garment.colors || []).find(c => c.id === String(b.color || '')) || garment.colors[0];
@@ -863,16 +883,15 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
               const ad = sd.address || {};
               order.recipient = { name: sd.name || null, address1: ad.line1 || null, address2: ad.line2 || null,
                 city: ad.city || null, state_code: ad.state || null, country_code: ad.country || null, zip: ad.postal_code || null };
-              // DRAFT the POD order — do NOT submit. This is the exact payload a
-              // future Printful call will send; scripts/submit-pod-draft.mjs
-              // is the Steve-gated sender (Printful, confirm=false drafts).
+              // DRAFT the POD order — do NOT submit. The selected provider's
+              // separately gated sender validates and transmits it later.
               if (!order.pod_submitted) {
                 const sig = (await mergedSignatures()).find(x => qidOf(x) === order.qid);
-                const draft = { draftedAt: new Date().toISOString(), orderId: order.id, status: 'DRAFT_UNSENT', provider: 'printful',
+                const draft = { draftedAt: new Date().toISOString(), orderId: order.id, status: 'DRAFT_UNSENT', provider: wearPodProvider(),
                   recipient_email: order.email, recipient: order.recipient, garment: order.garment, color: order.color, size: order.size,
                   placement: order.placement, signature_name: order.signature_name,
                   design_image_url: sig ? sig.signature_image_url : null,
-                  note: 'NOT SENT — awaiting Steve approval + PRINTFUL_API_KEY + WEAR_SALES_LIVE=1 (scripts/submit-pod-draft.mjs --apply)' };
+                  note: `NOT SENT — awaiting Steve approval + ${wearPodProvider()} credentials/mapping + WEAR_SALES_LIVE=1` };
                 await appendFile(join(DATA, 'pod-order-drafts.jsonl'), JSON.stringify(draft) + '\n');
                 order.pod_submitted = true;
               }
diff --git a/test/printify-pod.test.mjs b/test/printify-pod.test.mjs
new file mode 100644
index 0000000..b458653
--- /dev/null
+++ b/test/printify-pod.test.mjs
@@ -0,0 +1,33 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { buildPayload } from '../scripts/submit-pod-draft-printify.mjs';
+
+const template = { garments: [{ id: 'classic-tee', printify: {
+  blueprintId: 9, printProviderId: 5,
+  printPosition: { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 },
+  variants: { 'white/2XL': 17887 },
+} }] };
+const draft = { orderId: 5001, garment: 'classic-tee', color: 'white', size: '2XL',
+  signature_name: 'Thomas Jefferson', design_image_url: 'https://example.com/signature.png',
+  recipient_email: 'proof@example.com', recipient: { name: 'Test Buyer', address1: '1 Main St',
+    city: 'Los Angeles', state_code: 'CA', country_code: 'US', zip: '90001' } };
+
+test('builds a dynamic Printify left-chest order payload', () => {
+  const payload = buildPayload(template, draft);
+  assert.equal(payload.line_items[0].blueprint_id, 9);
+  assert.equal(payload.line_items[0].print_provider_id, 5);
+  assert.equal(payload.line_items[0].variant_id, 17887);
+  assert.deepEqual(payload.line_items[0].print_areas.front, [{
+    src: draft.design_image_url, scale: 0.25, x: 0.32, y: 0.3, angle: 0,
+  }]);
+  assert.equal(payload.send_shipping_notification, false);
+});
+
+test('fails loud on unmapped variants', () => {
+  assert.throws(() => buildPayload(template, { ...draft, size: 'XL' }), /not mapped/);
+});
+
+test('fails loud without a design or complete shipping address', () => {
+  assert.throws(() => buildPayload(template, { ...draft, design_image_url: null }), /no design_image_url/);
+  assert.throws(() => buildPayload(template, { ...draft, recipient: { ...draft.recipient, zip: '' } }), /missing shipping fields zip/);
+});
diff --git a/verification/e2e-proof-printify.json b/verification/e2e-proof-printify.json
new file mode 100644
index 0000000..9d36cf5
--- /dev/null
+++ b/verification/e2e-proof-printify.json
@@ -0,0 +1,46 @@
+{
+  "intent": "Add a safely disabled Printify fulfillment option for TK-10286 while preserving the existing Printful path and preventing charges without fulfillment readiness.",
+  "riskTier": "R2 local user-facing and order-boundary change; live provider and money actions remain gated.",
+  "environment": "Local CelebritySignatures servers on ports 19921 and 19922; no Printify token, shop id, variant mapping, or live provider action.",
+  "baselineCommit": "4f0db6e2bf3a871de6fc88af62f0364b7c8c4aae",
+  "timestamp": "2026-09-03T20:54:52Z",
+  "assertions": [
+    {
+      "boundary": "unit/contract",
+      "check": "Dynamic Printify payload uses blueprint_id, print_provider_id, selected variant_id, and the paid order's signature image at front x=0.32, y=0.30, scale=0.25; missing mapping, artwork, or address fails loud.",
+      "verdict": "PASS",
+      "evidence": "node --test test/printify-pod.test.mjs: 3/3 passed"
+    },
+    {
+      "boundary": "provider sender",
+      "check": "Without credentials the Printify sender reports zero pending and DRY RUN. POST requires --apply, PRINTIFY_API_TOKEN, PRINTIFY_SHOP_ID, WEAR_SALES_LIVE=1, and PRINTIFY_MANUAL_APPROVAL_CONFIRMED=1.",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "payment negative path",
+      "check": "With WEAR_SALES_LIVE=1 and WEAR_POD_PROVIDER=printify artificially set, but no token/mappings, checkout returns HTTP 503 apparel fulfillment is not configured before any Stripe or order write.",
+      "verdict": "PASS",
+      "evidence": "data/wear-orders.json SHA-1 remained dc87075a6a70b90c42723986b372eed8df7fc6f3 before and after"
+    },
+    {
+      "boundary": "UI regression",
+      "check": "/wear rendered 2955 cards; opening a card showed the left-chest preview; zero JavaScript errors; HTTP, headless paint, Playwright interaction, and installed Chrome passed.",
+      "verdict": "PASS",
+      "artifact": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/3x-99EGa9"
+    },
+    {
+      "boundary": "cross-browser",
+      "check": "Safari and Firefox automation were unavailable with --no-open.",
+      "verdict": "SKIP",
+      "reason": "Non-critical parity check; four independent installed layers passed."
+    },
+    {
+      "boundary": "external account",
+      "check": "Real Printify shop/catalog discovery and shipping/order validation.",
+      "verdict": "SKIP",
+      "reason": "PRINTIFY_API_TOKEN is not stored; this is a critical activation prerequisite, so live readiness is not claimed."
+    }
+  ],
+  "cleanup": "Temporary local servers stopped. No Stripe session, local order, Printify request, production job, deployment, publish, DNS change, or remote push occurred.",
+  "overallVerdict": "PASS for disabled adapter and fail-closed checkout. PARTIAL/BLOCKED for real account integration until Steve provides or authorizes creation of a scoped Printify personal access token and explicitly approves later live activation."
+}

← 4f0db6e document TK-10286 wear end-to-end proof  ·  back to CelebritySignatures  ·  Celebrity Signatures: correct a FALSE 'Data Not Collected' p 919eed1 →