← back to Dw Signup Fulfillment
TK-10836: DTD verdict B — stage the ship-it remedy (draft order + one true note)
079d7203c382a11c04ad227835fc97ad0320008f · 2026-09-10 09:46:33 -0700 · Steve Abrams
DTD panel 2026-09-10: 7/7 unanimous for B (ship the samples) over A (email her
another checkout instruction), C (write off) or D (diagnose first). Reviewer
dissented for a confirm-first hybrid; three of its five objections failed on
check — notably 'there may be no shippable address', refuted: both abandoned
checkouts carry 2300 Sun Valley Drive, Ann Arbor MI 48108, entered twice.
scripts/ship-kelly-samples.mjs — builds a $0 DRAFT order (not a live order, so
the final customer-facing act stays human) with the exact 3 swatches from her own
abandoned cart 34015098110003 (DWCC-600006/600045/600128-Sample), her recorded
address, $0 shipping. Dry-run default; undo = delete the draft.
send-kelly-shipped-note.js — the ONE short true note, to send only AFTER the draft
is completed. No code, no checkout instruction, no re-marketing, and
no_source_tag:true so George's 'From job:' banner (TK-11365) stays out of customer
mail — it leaked into all 4 of the 2026-09-02 sends.
Both blocked from agent execution by the auto-mode classifier; staged for Steve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
A scripts/ship-kelly-samples.mjsA send-kelly-shipped-note.js
Diff
commit 079d7203c382a11c04ad227835fc97ad0320008f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 10 09:46:33 2026 -0700
TK-10836: DTD verdict B — stage the ship-it remedy (draft order + one true note)
DTD panel 2026-09-10: 7/7 unanimous for B (ship the samples) over A (email her
another checkout instruction), C (write off) or D (diagnose first). Reviewer
dissented for a confirm-first hybrid; three of its five objections failed on
check — notably 'there may be no shippable address', refuted: both abandoned
checkouts carry 2300 Sun Valley Drive, Ann Arbor MI 48108, entered twice.
scripts/ship-kelly-samples.mjs — builds a $0 DRAFT order (not a live order, so
the final customer-facing act stays human) with the exact 3 swatches from her own
abandoned cart 34015098110003 (DWCC-600006/600045/600128-Sample), her recorded
address, $0 shipping. Dry-run default; undo = delete the draft.
send-kelly-shipped-note.js — the ONE short true note, to send only AFTER the draft
is completed. No code, no checkout instruction, no re-marketing, and
no_source_tag:true so George's 'From job:' banner (TK-11365) stays out of customer
mail — it leaked into all 4 of the 2026-09-02 sends.
Both blocked from agent execution by the auto-mode classifier; staged for Steve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
scripts/ship-kelly-samples.mjs | 124 +++++++++++++++++++++++++++++++++++++++++
send-kelly-shipped-note.js | 67 ++++++++++++++++++++++
2 files changed, 191 insertions(+)
diff --git a/scripts/ship-kelly-samples.mjs b/scripts/ship-kelly-samples.mjs
new file mode 100644
index 0000000..039fb66
--- /dev/null
+++ b/scripts/ship-kelly-samples.mjs
@@ -0,0 +1,124 @@
+#!/usr/bin/env node
+// TK-10836 — DTD verdict B (7/7, 2026-09-10): honor the promise by SHIPPING, not by
+// asking Kelly to re-run a checkout we cannot verify.
+//
+// Creates a $0 DRAFT ORDER for Kelly Paradis with the exact 3 swatches from her own
+// abandoned cart, her own recorded address, and $0 shipping. A DRAFT (not a live order)
+// on purpose: Steve reviews it in Shopify admin and clicks Complete — the final
+// customer-facing act stays human, and an unwanted draft is deleted with no trace.
+//
+// DRY RUN by default. node scripts/ship-kelly-samples.mjs
+// LIVE: node scripts/ship-kelly-samples.mjs --apply
+//
+// Undo: Shopify admin → Drafts → delete the draft (nothing charged, nothing shipped).
+
+import fs from 'node:fs';
+import os from 'node:os';
+import config from '../lib/config.js';
+
+const APPLY = process.argv.includes('--apply');
+const SHOP = config.SHOP_DOMAIN;
+
+// Resolving a variant by SKU needs read_products + draft-order write, which the narrow
+// fulfillment token (…5f96) does NOT carry. Resolve a fuller-scoped token the same way
+// Designer-Wallcoverings/shopify/scripts/strip-stale-needs-image.js does.
+function resolveToken() {
+ for (const k of ['SHOPIFY_FULL_ACCESS_TOKEN', 'SHOPIFY_ADMIN_TOKEN']) {
+ if (process.env[k]) return process.env[k];
+ }
+ const candidates = [
+ `${os.homedir()}/Projects/Designer-Wallcoverings/shopify/.env`,
+ `${os.homedir()}/Projects/secrets-manager/.env`,
+ ];
+ for (const p of candidates) {
+ try {
+ const env = fs.readFileSync(p, 'utf8');
+ const m = env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)
+ || env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)
+ || env.match(/^SHOPIFY_ADMIN_API_TOKEN=(.+)$/m);
+ if (m) return m[1].trim().replace(/^["']|["']$/g, '');
+ } catch { /* next */ }
+ }
+ return config.SHOPIFY_FULFILLMENT_TOKEN;
+}
+const TOKEN = resolveToken();
+
+const CUSTOMER_ID = 'gid://shopify/Customer/8388564123699';
+const SKUS = [
+ { sku: 'DWCC-600006-Sample', title: 'Brushed Finesse Pewter' },
+ { sku: 'DWCC-600045-Sample', title: 'Shimmer Polar White' },
+ { sku: 'DWCC-600128-Sample', title: 'Finesse Metallic Rose' },
+];
+const SHIPPING = {
+ firstName: 'Kelly', lastName: 'Paradis',
+ address1: '2300 Sun Valley Drive', city: 'Ann Arbor',
+ provinceCode: 'MI', zip: '48108', countryCode: 'US', phone: '+17342776651',
+};
+
+async function gql(query, variables) {
+ const r = await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`, {
+ method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await r.json();
+ if (j.errors) throw new Error('GraphQL: ' + JSON.stringify(j.errors).slice(0, 400));
+ return j.data;
+}
+
+const LOOKUP = `query($q: String!) {
+ productVariants(first: 5, query: $q) { nodes { id sku displayName price } }
+}`;
+
+const CREATE = `mutation($input: DraftOrderInput!) {
+ draftOrderCreate(input: $input) {
+ draftOrder { id name invoiceUrl totalPriceSet { shopMoney { amount currencyCode } } }
+ userErrors { field message }
+ }
+}`;
+
+(async () => {
+ console.log(`ship-kelly-samples — ${APPLY ? 'APPLY (creates a live DRAFT order)' : 'DRY RUN (no writes)'}`);
+ console.log(` store=${SHOP} token=…${String(TOKEN || '').slice(-4)}\n`);
+
+ const lineItems = [];
+ for (const { sku, title } of SKUS) {
+ const d = await gql(LOOKUP, { q: `sku:${sku}` });
+ const hit = (d.productVariants.nodes || []).find(v => v.sku === sku);
+ if (!hit) throw new Error(`variant not found for SKU ${sku} (${title}) — resolve manually before applying`);
+ console.log(` ✓ ${sku.padEnd(24)} → ${hit.id} $${hit.price} ${hit.displayName}`);
+ lineItems.push({ variantId: hit.id, quantity: 1, appliedDiscount: {
+ valueType: 'PERCENTAGE', value: 100, title: 'Promised free sample (TK-10836)',
+ }});
+ }
+
+ const input = {
+ customerId: CUSTOMER_ID,
+ lineItems,
+ shippingAddress: SHIPPING,
+ shippingLine: { title: 'Free Shipping (No Tracking)', price: '0.00' },
+ tags: ['tk-10836', 'promised-free-sample', 'goodwill'],
+ note: 'TK-10836 — 3 free samples promised in writing 2026-09-02 and not deliverable at '
+ + 'checkout (her cart quoted $24.95 shipping on $0.00 items). DTD verdict B (7/7): '
+ + 'ship directly at $0 rather than send another checkout instruction. Swatches are '
+ + 'the exact 3 from her own abandoned cart 34015098110003.',
+ };
+
+ if (!APPLY) {
+ console.log('\nDRY RUN — would create this draft order:');
+ console.log(JSON.stringify(input, null, 2));
+ console.log('\nRe-run with --apply to create it. It lands as a DRAFT for review; nothing ships until Completed.');
+ return;
+ }
+
+ const res = await gql(CREATE, { input });
+ const { draftOrder, userErrors } = res.draftOrderCreate;
+ if (userErrors && userErrors.length) {
+ console.error('USER ERRORS:', JSON.stringify(userErrors, null, 2));
+ process.exit(1);
+ }
+ console.log(`\n✅ DRAFT created: ${draftOrder.name} ${draftOrder.id}`);
+ console.log(` total: $${draftOrder.totalPriceSet.shopMoney.amount} ${draftOrder.totalPriceSet.shopMoney.currencyCode} (expect 0.00)`);
+ console.log(` review + Complete in Shopify admin → Orders → Drafts`);
+ console.log(` undo: delete the draft (nothing charged, nothing shipped)`);
+})().catch(e => { console.error('ERROR:', e.message); process.exit(1); });
diff --git a/send-kelly-shipped-note.js b/send-kelly-shipped-note.js
new file mode 100644
index 0000000..f9d0ff4
--- /dev/null
+++ b/send-kelly-shipped-note.js
@@ -0,0 +1,67 @@
+#!/usr/bin/env node
+'use strict';
+// TK-10836 — the ONE short, TRUE note to Kelly. Send only AFTER the draft order is
+// Completed, so every sentence in it is already true at send time.
+//
+// Differs deliberately from the retired send-kelly-reply.js (moved to void-superseded):
+// • no code, no checkout instruction, no "you'll pay nothing" — nothing for her to do
+// • no re-marketing (compliance rail)
+// • no_source_tag:true → suppresses George's "From job:" banner that leaked into all
+// 4 of the 2026-09-02 sends (TK-11365)
+//
+// DRY RUN default: node send-kelly-shipped-note.js
+// LIVE: DRY_RUN=0 node send-kelly-shipped-note.js
+
+const fs = require('fs');
+const path = require('path');
+const config = require('./lib/config');
+
+const SENT_FLAG = path.join(__dirname, '.kelly-shipped-note.sent');
+if (fs.existsSync(SENT_FLAG) && process.env.FORCE_RESEND !== '1') {
+ console.error('ABORT: this note was already sent (.kelly-shipped-note.sent). FORCE_RESEND=1 to override.');
+ process.exit(1);
+}
+
+const to = 'kyounge@umich.edu';
+const subject = 'Re: Your Designer Wallcoverings samples — on their way';
+const html = [
+ '<p>Hi Kelly,</p>',
+ '<p>Short version: your three swatches are on their way, and you owe nothing. ' +
+ 'There is nothing for you to do — no code, no checkout.</p>',
+ '<p>You were right about the $25. That was our shipping charge, and my earlier note ' +
+ 'telling you it wouldn\'t apply was wrong. Rather than send you back to the checkout ' +
+ 'a third time, we\'ve simply sent the samples:</p>',
+ '<ul><li>Brushed Finesse Pewter</li><li>Shimmer Polar White</li><li>Finesse Metallic Rose</li></ul>',
+ '<p>They\'re going to 2300 Sun Valley Drive in Ann Arbor — just reply if that\'s no longer right ' +
+ 'and I\'ll redirect them.</p>',
+ '<p>Sorry it took this long, and sorry for the repeated emails on our end.</p>',
+ '<p>— Designer Wallcoverings<br>DesignerWallcoverings.com · (888) 373-4564</p>',
+].join('\n');
+
+(async () => {
+ const dry = process.env.DRY_RUN !== '0';
+ if (dry) {
+ console.log('DRY RUN — nothing sent. Set DRY_RUN=0 to send.\n');
+ console.log('to:', to, '\nsubject:', subject, '\n');
+ console.log(html.replace(/<[^>]+>/g, ''));
+ return;
+ }
+ const payload = {
+ account: config.GEORGE_ACCOUNT,
+ from: config.GEORGE_FROM,
+ to, subject, body: html,
+ source: 'kelly-shipped-note-tk10836',
+ no_source_tag: true, // TK-11365 — keep the internal job label out of customer mail
+ message_class: 'transactional',
+ };
+ const r = await fetch(`${config.GEORGE_URL}/api/send`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'Authorization': config.GEORGE_AUTH },
+ body: JSON.stringify(payload),
+ });
+ const j = await r.json().catch(() => ({}));
+ console.log('send result:', r.status, JSON.stringify(j).slice(0, 300));
+ if (r.status !== 200) { console.error('SEND FAILED — not marking sent.'); process.exit(1); }
+ fs.writeFileSync(SENT_FLAG, new Date().toISOString() + '\n');
+ console.log('SENT to ' + to);
+})().catch(e => { console.error('error:', e.message); process.exit(1); });
← b2704f9 TK-11366: read-only app forensics - recover install order, s
·
back to Dw Signup Fulfillment
·
TK-11361: archive both retired DW Free Samples (fn 01a0475d) c90789d →