← back to Designerwallcoverings
TK-11471: inert executor so the gated backfill memo is actually approvable
0876e0f84e1391845ceeac12c917f97ea255c110 · 2026-09-11 11:48:22 -0700 · Steve Abrams
The pending-approval memo for the 83 live zero-weight variants named an "exact command"
pointing at a script that did not exist. A memo whose command cannot be run is not an
approvable memo — Steve would have had to approve a thing and then discover there was
nothing to run.
scripts/tk11471-weight-backfill.py is INERT: --apply REFUSES without a restore map
written by a prior --snapshot, so the undo provably exists BEFORE the write rather than
after. Verified refusal paths (real output, no Shopify calls made): missing restore map
-> rc=2; no mode flag -> rc=2; map already marked applied:true -> rc=2 (no double-apply).
- --snapshot aborts rather than writing a restore map if ANY sku resolves to 0 or >1
variants: an unmeasured target is never treated as safe.
- --apply skips variants that already carry a positive weight instead of overwriting
them, then RE-READS every row and reports any not confirmed > 0 rather than assuming
the mutation worked.
- --undo replays each recorded `before`; a row that was genuinely UNSET is CLEARED back
to unset, not stamped 0 — a stamped 0 would read downstream as "measured zero".
- Defaults are imported from lib/weight_guard.py (Steve's approved TK-11414 values)
rather than retyped, so the backfill cannot drift from what the gates enforce.
The write itself remains HARD-GATED and unrun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
Files touched
A scripts/tk11471-weight-backfill.py
Diff
commit 0876e0f84e1391845ceeac12c917f97ea255c110
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 11:48:22 2026 -0700
TK-11471: inert executor so the gated backfill memo is actually approvable
The pending-approval memo for the 83 live zero-weight variants named an "exact command"
pointing at a script that did not exist. A memo whose command cannot be run is not an
approvable memo — Steve would have had to approve a thing and then discover there was
nothing to run.
scripts/tk11471-weight-backfill.py is INERT: --apply REFUSES without a restore map
written by a prior --snapshot, so the undo provably exists BEFORE the write rather than
after. Verified refusal paths (real output, no Shopify calls made): missing restore map
-> rc=2; no mode flag -> rc=2; map already marked applied:true -> rc=2 (no double-apply).
- --snapshot aborts rather than writing a restore map if ANY sku resolves to 0 or >1
variants: an unmeasured target is never treated as safe.
- --apply skips variants that already carry a positive weight instead of overwriting
them, then RE-READS every row and reports any not confirmed > 0 rather than assuming
the mutation worked.
- --undo replays each recorded `before`; a row that was genuinely UNSET is CLEARED back
to unset, not stamped 0 — a stamped 0 would read downstream as "measured zero".
- Defaults are imported from lib/weight_guard.py (Steve's approved TK-11414 values)
rather than retyped, so the backfill cannot drift from what the gates enforce.
The write itself remains HARD-GATED and unrun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
---
scripts/tk11471-weight-backfill.py | 216 +++++++++++++++++++++++++++++++++++++
1 file changed, 216 insertions(+)
diff --git a/scripts/tk11471-weight-backfill.py b/scripts/tk11471-weight-backfill.py
new file mode 100644
index 0000000..0b57880
--- /dev/null
+++ b/scripts/tk11471-weight-backfill.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+"""tk11471-weight-backfill.py — GATED executor for the 83 ACTIVE zero-weight variants.
+
+This script is INERT until run with --apply, and --apply REFUSES to run without a restore
+map written by a prior --snapshot pass. It exists so the pending-approval memo
+(tk11471-zero-weight-active-variant-backfill.md) has a command that actually exists:
+a memo whose "exact command" points at a missing file is not an approvable memo.
+
+Steve's rule (TK-11414): NO product may be ACTIVE with missing/zero weight — zero weight
+collapses an order into the lowest weight tier / free-shipping band and mis-costs freight.
+
+THE WRITE IS CUSTOMER-FACING AND HARD-GATED. Do not run --apply without Steve's go.
+
+ # phase 1 — snapshot only. Writes NOTHING to Shopify. Safe to run any time.
+ python3 scripts/tk11471-weight-backfill.py --snapshot \
+ --offenders ~/.claude/yolo-queue/pending-approval/tk11471-zero-weight-offenders-20260911T1818Z.json \
+ --restore-map ~/.claude/yolo-queue/executed-reversible/tk11471-weight-restore-map.json
+
+ # phase 2 — the gated write. Requires the restore map from phase 1.
+ python3 scripts/tk11471-weight-backfill.py --apply --restore-map <same path>
+
+ # undo — replay the restore map, writing each recorded `before` back.
+ python3 scripts/tk11471-weight-backfill.py --undo --restore-map <same path>
+
+Design notes that matter (CLAUDE.md TK-11431):
+ * An UNMEASURED variant is never treated as done. A SKU that resolves to 0 or >1 variants
+ aborts the snapshot rather than being guessed at.
+ * VERIFY re-reads after the write. A variant not confirmed >0 is REPORTED, never assumed.
+ * The undo restores the recorded `before` — including `null`, which clears the measurement
+ back to genuinely-unset rather than stamping a 0 that would read as "measured zero".
+ * Defaults are the already-Steve-approved TK-11414 values, imported from the shared guard
+ rather than retyped, so this cannot drift from what the gates enforce.
+"""
+import argparse, json, os, re, sys, time, urllib.request, urllib.error
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib'))
+from weight_guard import SAMPLE_WEIGHT_LB, TYPE_DEFAULT_LB, FALLBACK_LB, is_sample_variant # noqa: E402
+
+ENV = os.path.expanduser('~/Projects/secrets-manager/.env')
+API = '2024-10'
+
+
+def creds():
+ env = open(ENV).read()
+ store = re.search(r'^SHOPIFY_STORE=(.+)$', env, re.M).group(1).strip()
+ # inventoryItemUpdate needs write_inventory, which the narrow SHOPIFY_ADMIN_TOKEN lacks.
+ tok = os.environ.get('SHOPIFY_TOKEN') or re.search(r'^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$', env, re.M).group(1).strip()
+ return store, tok
+
+
+def gql(store, tok, query, variables=None):
+ body = json.dumps({'query': query, 'variables': variables or {}}).encode()
+ req = urllib.request.Request(f'https://{store}/admin/api/{API}/graphql.json', data=body,
+ headers={'X-Shopify-Access-Token': tok, 'Content-Type': 'application/json'})
+ for attempt in range(5):
+ try:
+ with urllib.request.urlopen(req, timeout=30) as r:
+ j = json.loads(r.read())
+ if j.get('errors'):
+ if 'THROTTLED' in json.dumps(j['errors']):
+ time.sleep(2 * (attempt + 1)); continue
+ raise RuntimeError(json.dumps(j['errors'])[:300])
+ return j['data']
+ except urllib.error.HTTPError as e:
+ if e.code in (429, 500, 502, 503):
+ time.sleep(2 * (attempt + 1)); continue
+ raise
+ raise RuntimeError('exhausted retries')
+
+
+Q_VARIANT = '''query($q:String!){ productVariants(first:5, query:$q){ nodes{
+ id sku title
+ product{ id title productType status }
+ inventoryItem{ id measurement{ weight{ value unit } } } } } }'''
+
+M_SET = '''mutation($id:ID!,$w:Float!){ inventoryItemUpdate(id:$id,
+ input:{measurement:{weight:{value:$w, unit:POUNDS}}}){
+ inventoryItem{ id measurement{ weight{ value unit } } } userErrors{ message } } }'''
+
+M_CLEAR = '''mutation($id:ID!){ inventoryItemUpdate(id:$id,
+ input:{measurement:{weight:null}}){ inventoryItem{ id } userErrors{ message } } }'''
+
+
+def target_lb(variant, product_type):
+ if is_sample_variant(variant):
+ return SAMPLE_WEIGHT_LB
+ return TYPE_DEFAULT_LB.get(product_type, FALLBACK_LB)
+
+
+def snapshot(store, tok, offenders_path, map_path):
+ offenders = json.load(open(offenders_path))
+ skus = sorted({o['sku'] for o in offenders if o.get('sku')})
+ print(f'snapshot: {len(skus)} distinct SKUs from {len(offenders)} offender rows')
+ rows, aborts = [], []
+ for i, sku in enumerate(skus, 1):
+ d = gql(store, tok, Q_VARIANT, {'q': f'sku:{sku}'})
+ nodes = [n for n in d['productVariants']['nodes'] if n.get('sku') == sku]
+ if len(nodes) != 1:
+ aborts.append({'sku': sku, 'matched': len(nodes)})
+ print(f' ABORT-CANDIDATE {sku}: resolved to {len(nodes)} variants (need exactly 1)')
+ continue
+ n = nodes[0]
+ w = (n.get('inventoryItem') or {}).get('measurement', {}) or {}
+ w = (w or {}).get('weight')
+ rows.append({
+ 'sku': sku, 'variantId': n['id'], 'inventoryItemId': n['inventoryItem']['id'],
+ 'productId': n['product']['id'], 'productType': n['product'].get('productType'),
+ 'productStatus': n['product'].get('status'), 'isSample': is_sample_variant(n),
+ 'before': ({'value': w.get('value'), 'unit': w.get('unit')} if w else None),
+ 'target_lb': target_lb(n, n['product'].get('productType')),
+ })
+ if i % 20 == 0:
+ print(f' ...{i}/{len(skus)}')
+ time.sleep(0.15)
+ if aborts:
+ print(f'\nREFUSING to write a restore map: {len(aborts)} SKU(s) did not resolve to exactly one '
+ f'variant. An unmeasured target is never treated as safe. Resolve these first:')
+ for a in aborts:
+ print(f' {a["sku"]} -> {a["matched"]} matches')
+ return 2
+ os.makedirs(os.path.dirname(map_path), exist_ok=True)
+ json.dump({'ticket': 'TK-11471', 'created_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
+ 'applied': False, 'rows': rows}, open(map_path, 'w'), indent=2)
+ already = [r for r in rows if r['before'] and (r['before'].get('value') or 0) > 0]
+ print(f'\nrestore map written: {map_path} ({len(rows)} rows)')
+ if already:
+ print(f'NOTE: {len(already)} already carry a positive weight (healed since the 18:18Z export) '
+ f'— they will be SKIPPED by --apply, not overwritten.')
+ return 0
+
+
+def apply_(store, tok, map_path):
+ if not os.path.exists(map_path):
+ print(f'REFUSING: no restore map at {map_path}. Run --snapshot first — the undo must exist '
+ f'BEFORE the write, not after.'); return 2
+ m = json.load(open(map_path))
+ if m.get('applied'):
+ print('restore map already marked applied=true; refusing to re-apply. Use --undo to reverse.'); return 2
+ rows = m['rows']
+ wrote, skipped, failed = 0, 0, []
+ for r in rows:
+ if r['before'] and (r['before'].get('value') or 0) > 0:
+ skipped += 1; continue
+ d = gql(store, tok, M_SET, {'id': r['inventoryItemId'], 'w': float(r['target_lb'])})
+ errs = d['inventoryItemUpdate']['userErrors']
+ if errs:
+ failed.append({'sku': r['sku'], 'errors': errs}); continue
+ wrote += 1
+ time.sleep(0.15)
+ m['applied'] = True
+ m['applied_at'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ json.dump(m, open(map_path, 'w'), indent=2)
+ print(f'APPLY: wrote {wrote}, skipped {skipped} (already positive), failed {len(failed)}')
+
+ # VERIFY — re-read. A variant not confirmed > 0 is reported, never assumed.
+ unconfirmed = []
+ for r in rows:
+ d = gql(store, tok, Q_VARIANT, {'q': f'sku:{r["sku"]}'})
+ nodes = [n for n in d['productVariants']['nodes'] if n.get('sku') == r['sku']]
+ if len(nodes) != 1:
+ unconfirmed.append({'sku': r['sku'], 'reason': f'{len(nodes)} matches on re-read'}); continue
+ w = ((nodes[0].get('inventoryItem') or {}).get('measurement') or {}).get('weight')
+ val = (w or {}).get('value')
+ if not (isinstance(val, (int, float)) and val > 0):
+ unconfirmed.append({'sku': r['sku'], 'reason': f'still {val!r} after write'})
+ time.sleep(0.1)
+ if unconfirmed:
+ print(f'\nVERIFY FAILED for {len(unconfirmed)}:')
+ for u in unconfirmed:
+ print(f' {u["sku"]}: {u["reason"]}')
+ return 1
+ print(f'VERIFY: all {len(rows)} confirmed weight > 0.')
+ return 0 if not failed else 1
+
+
+def undo(store, tok, map_path):
+ m = json.load(open(map_path))
+ restored = 0
+ for r in m['rows']:
+ b = r['before']
+ if b and b.get('value') is not None:
+ gql(store, tok, M_SET, {'id': r['inventoryItemId'], 'w': float(b['value'])})
+ else:
+ # restore genuinely-unset, NOT a literal 0 — a stamped 0 reads as "measured zero".
+ gql(store, tok, M_CLEAR, {'id': r['inventoryItemId']})
+ restored += 1
+ time.sleep(0.15)
+ m['applied'] = False
+ m['undone_at'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ json.dump(m, open(map_path, 'w'), indent=2)
+ print(f'UNDO: restored {restored} variants to their recorded prior state.')
+ return 0
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('--offenders'); ap.add_argument('--restore-map', required=True)
+ ap.add_argument('--snapshot', action='store_true')
+ ap.add_argument('--apply', action='store_true')
+ ap.add_argument('--undo', action='store_true')
+ a = ap.parse_args()
+ if sum([a.snapshot, a.apply, a.undo]) != 1:
+ print('pick exactly one of --snapshot / --apply / --undo'); return 2
+ store, tok = creds()
+ if a.snapshot:
+ if not a.offenders:
+ print('--snapshot needs --offenders'); return 2
+ return snapshot(store, tok, os.path.expanduser(a.offenders), os.path.expanduser(a.restore_map))
+ if a.apply:
+ print('*** CUSTOMER-FACING SHOPIFY WRITE — this must be Steve-approved (TK-11471). ***')
+ return apply_(store, tok, os.path.expanduser(a.restore_map))
+ return undo(store, tok, os.path.expanduser(a.restore_map))
+
+
+if __name__ == '__main__':
+ sys.exit(main())
← ea8f871 TK-11471: weight gate on the JD rolling publish (the 4th ung
·
back to Designerwallcoverings
·
TK-11461 refinement: closed population + exact 76-image sele 34e4591 →