[object Object]

← 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

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 →