← back to Dw Material Envelope Canary
check-bulk.py
77 lines
#!/usr/bin/env python3
"""WEEKLY exhaustive pass — the durable net the daily check.py can't be.
Runs a Shopify bulk operation exporting EVERY metafield on EVERY product, then
scans every value with the robust substring detector (catches scalar, list, and
double-escaped envelope variants). No curated key list. CNCP alert on FAIL.
READ-ONLY. Born 2026-07-07 from the /yolo contrarian's 'watch all keys' verdict."""
import os, json, sys, time, urllib.request, datetime
HERE = os.path.dirname(os.path.abspath(__file__))
def load_env():
for p in [os.path.expanduser("~/Projects/Designer-Wallcoverings/.env"), os.path.join(HERE, ".env")]:
if os.path.exists(p):
for ln in open(p):
if "=" in ln and not ln.strip().startswith("#"):
k, v = ln.strip().split("=", 1)
os.environ.setdefault(k, v.strip().strip('"').strip("'"))
load_env()
DOMAIN = os.environ.get("SHOPIFY_STORE_DOMAIN", "designer-laboratory-sandbox.myshopify.com")
TOK = os.environ.get("SHOPIFY_ADMIN_TOKEN") or os.environ.get("SHOPIFY_ADMIN_ACCESS_TOKEN")
if not TOK:
sys.exit("ERROR: SHOPIFY_ADMIN_TOKEN not set (check ~/Projects/Designer-Wallcoverings/.env)")
URL = f"https://{DOMAIN}/admin/api/2024-01/graphql.json"
CNCP = "http://127.0.0.1:3333/api/parking_lot"
def gql(q):
b = json.dumps({"query": q}).encode()
return json.loads(urllib.request.urlopen(urllib.request.Request(
URL, data=b, headers={"X-Shopify-Access-Token": TOK, "Content-Type": "application/json"}), timeout=60).read())
def is_env(v):
return isinstance(v, str) and "single_line_text_field" in v
START = 'mutation { bulkOperationRunQuery(query: """{ products { edges { node { id handle metafields { edges { node { namespace key value } } } } } } }""") { bulkOperation { id status } userErrors { message } } }'
POLL = '{ currentBulkOperation { status url objectCount } }'
def main():
os.makedirs(os.path.join(HERE, "data"), exist_ok=True)
r = gql(START)
ue = r["data"]["bulkOperationRunQuery"]["userErrors"]
if ue:
print("start err", ue); return 2
while True:
op = gql(POLL)["data"]["currentBulkOperation"]
if op["status"] in ("COMPLETED", "FAILED", "CANCELED"): break
time.sleep(20)
if op["status"] != "COMPLETED" or not op.get("url"):
print("bulk not completed:", op); return 2
path = os.path.join(HERE, "data", "bulk.jsonl")
urllib.request.urlretrieve(op["url"], path)
hits = {}; nprod = 0; nmf = 0
with open(path) as fh:
for ln in fh:
try:
o = json.loads(ln)
except (json.JSONDecodeError, ValueError):
continue
if "namespace" in o and "key" in o:
nmf += 1
if is_env(o.get("value")):
field_key = f'{o["namespace"]}.{o["key"]}'
hits[field_key] = hits.get(field_key, 0) + 1
elif "handle" in o:
nprod += 1
os.remove(path) # 800MB — don't keep
verdict = "FAIL" if hits else "PASS"
with open(os.path.join(HERE, "data", "latest-bulk.json"), "w") as fh:
json.dump({"ts": datetime.datetime.now().isoformat(), "products": nprod, "metafields": nmf,
"verdict": verdict, "hits": hits}, fh, indent=1)
print(f"{verdict}: {nprod} products, {nmf} metafields, {sum(hits.values())} enveloped across {len(hits)} keys")
if hits:
try:
card = {"title": f"⚠️ Metafield envelope regression (weekly bulk): {sum(hits.values())} values across {len(hits)} keys",
"note": f"dw-material-envelope-canary weekly bulk scan found leaked JSON envelopes: {json.dumps(hits)[:300]}. Re-run the unwrap fix + find the importer.",
"source": "dw-material-envelope-canary-bulk"}
urllib.request.urlopen(urllib.request.Request(CNCP, data=json.dumps(card).encode(),
headers={"Content-Type": "application/json"}), timeout=5)
print(" posted CNCP alert")
except Exception as ex: print(" CNCP post failed:", ex)
return 1 if hits else 0
if __name__ == "__main__":
sys.exit(main())