← back to Designerwallcoverings
TK-11483: receipt guard stops the daily Fentucci duplicate mint
81ba2dd2fabe53e82fd7053b8e4f69e4a037b6d8 · 2026-09-11 12:38:25 -0700 · Steve Abrams
MEASURED, not inferred. find_grs() resolves product identity by SKU alone, and a
competing writer re-stamps a freshly created GRS-xxxxx sellable SKU to DWFE-xxxx.
So the next morning's sku:GRS-xxxxx search legitimately finds nothing, concludes
the product is absent, and creates it again - every day, at 16:22Z, from the
launchd job com.steve.dwpw-grs-daily.
Evidence: 52 GRS codes carry MORE THAN ONE create_grs_draft receipt (145 redundant
creates); 288 of 292 live duplicate products join to a receipt this producer wrote
about itself; 52 of 52 products created 2026-09-11 already had a prior receipt.
Fix, two parts:
* require_no_prior_creation() re-anchors identity on OUR OWN creation receipts,
which no external SKU rewrite can reach. Prior receipt -> HISTORY_HOLD, row
skipped, nothing created.
* find_grs() is now fail-closed. Its None is read as "create one", so a None from
an INCOMPLETE read mints a duplicate. GraphQL errors, a missing edges list, a
truncated SKU-search page, or a dismissed candidate whose own variants were
paginated away now all raise LOOKUP_HOLD instead of returning None.
Ledger reads are fail-closed BY RELEVANCE, not over the whole file: the shared
executed-reversible ledger demonstrably holds a few plain-text lines from other
agents (2 of 30,650), and holding on those would brick the migration entirely
instead of blocking duplicates. An unparseable line that could be one of OUR
receipts still holds; a foreign one is skipped.
Scope stated honestly: this is NOT global idempotency. A create that crashed after
productCreate but before its receipt, and a concurrent second writer, remain
uncovered. It closes the measured daily-mint path.
Tests (scripts/tests/test_receipt_guard.py, 21 offline, network tripwire armed):
the load-bearing case is RED-BEFORE / GREEN-AFTER on the SAME injected fault -
guard off reaches productCreate, guard on holds and creates nothing - plus positive
controls so an always-deny guard could not pass, a full CLI-entrypoint run, and a
replay against the REAL ledger showing all 52 of today's creates would be held.
Preserves the TK-11414/TK-11471 weight gate, which the prepared candidate 7fe6f885
would have reverted (it was cut from a stale base).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Jc7e5vG4Jx68iXSYhwUiA
Files touched
M scripts/dwpw-grs-migrate.pyA scripts/tests/test_receipt_guard.py
Diff
commit 81ba2dd2fabe53e82fd7053b8e4f69e4a037b6d8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 12:38:25 2026 -0700
TK-11483: receipt guard stops the daily Fentucci duplicate mint
MEASURED, not inferred. find_grs() resolves product identity by SKU alone, and a
competing writer re-stamps a freshly created GRS-xxxxx sellable SKU to DWFE-xxxx.
So the next morning's sku:GRS-xxxxx search legitimately finds nothing, concludes
the product is absent, and creates it again - every day, at 16:22Z, from the
launchd job com.steve.dwpw-grs-daily.
Evidence: 52 GRS codes carry MORE THAN ONE create_grs_draft receipt (145 redundant
creates); 288 of 292 live duplicate products join to a receipt this producer wrote
about itself; 52 of 52 products created 2026-09-11 already had a prior receipt.
Fix, two parts:
* require_no_prior_creation() re-anchors identity on OUR OWN creation receipts,
which no external SKU rewrite can reach. Prior receipt -> HISTORY_HOLD, row
skipped, nothing created.
* find_grs() is now fail-closed. Its None is read as "create one", so a None from
an INCOMPLETE read mints a duplicate. GraphQL errors, a missing edges list, a
truncated SKU-search page, or a dismissed candidate whose own variants were
paginated away now all raise LOOKUP_HOLD instead of returning None.
Ledger reads are fail-closed BY RELEVANCE, not over the whole file: the shared
executed-reversible ledger demonstrably holds a few plain-text lines from other
agents (2 of 30,650), and holding on those would brick the migration entirely
instead of blocking duplicates. An unparseable line that could be one of OUR
receipts still holds; a foreign one is skipped.
Scope stated honestly: this is NOT global idempotency. A create that crashed after
productCreate but before its receipt, and a concurrent second writer, remain
uncovered. It closes the measured daily-mint path.
Tests (scripts/tests/test_receipt_guard.py, 21 offline, network tripwire armed):
the load-bearing case is RED-BEFORE / GREEN-AFTER on the SAME injected fault -
guard off reaches productCreate, guard on holds and creates nothing - plus positive
controls so an always-deny guard could not pass, a full CLI-entrypoint run, and a
replay against the REAL ledger showing all 52 of today's creates would be held.
Preserves the TK-11414/TK-11471 weight gate, which the prepared candidate 7fe6f885
would have reverted (it was cut from a stale base).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Jc7e5vG4Jx68iXSYhwUiA
---
scripts/dwpw-grs-migrate.py | 118 ++++++++++-
scripts/tests/test_receipt_guard.py | 385 ++++++++++++++++++++++++++++++++++++
2 files changed, 499 insertions(+), 4 deletions(-)
diff --git a/scripts/dwpw-grs-migrate.py b/scripts/dwpw-grs-migrate.py
index 89aa725..587d573 100644
--- a/scripts/dwpw-grs-migrate.py
+++ b/scripts/dwpw-grs-migrate.py
@@ -45,6 +45,7 @@ import argparse
import datetime
import json
import os
+import re
import time
import urllib.error
import urllib.request
@@ -190,7 +191,7 @@ def metafields_for(row):
_PRODUCT_FIELDS = '''
id handle status featuredImage{ url }
media(first:20){ edges{ node{ mediaContentType status ... on MediaImage { id } } } }
- variants(first:10){ edges{ node{ id sku title price position inventoryItem{ tracked } } } }'''
+ variants(first:10){ pageInfo{ hasNextPage } edges{ node{ id sku title price position inventoryItem{ tracked } } } }'''
def _media_info(n):
"""Return (has_image_media, [statuses]) for a product node's IMAGE media."""
@@ -217,16 +218,121 @@ def _parse_node(n):
"has_image": featured or has_media}
def find_grs(grs):
- """Return the shared product dict (see _parse_node) or None, via the SKU search index."""
- q = '''query($qy:String!){ products(first:5, query:$qy){ edges{ node{''' + _PRODUCT_FIELDS + ''' } } } }'''
+ """Return the shared product dict (see _parse_node) or None, via the SKU search index.
+
+ TK-11483 FAIL-CLOSED CONTRACT. This function's None is read by process() as
+ "no such product exists, create one", so a None produced by an INCOMPLETE read
+ is indistinguishable from a genuine absence and mints a duplicate. Every way the
+ read can be short — a GraphQL error, a missing edges list, a truncated SKU-search
+ page, or a non-matching product whose own variants were paginated away — now
+ raises LOOKUP_HOLD instead of returning None. The caller skips the row (ROW_ERROR)
+ rather than creating. An unmeasured lookup is never treated as an absence.
+ """
+ q = '''query($qy:String!){ products(first:5, query:$qy){ pageInfo{ hasNextPage } edges{ node{''' + _PRODUCT_FIELDS + ''' } } } }'''
d = gql(q, {"qy": f"sku:{grs}"})
- for e in d.get("data", {}).get("products", {}).get("edges", []):
+ if not isinstance(d, dict) or d.get("errors"):
+ raise RuntimeError("LOOKUP_HOLD: failed product lookup")
+ data = d.get("data")
+ products = data.get("products") if isinstance(data, dict) else None
+ if not isinstance(products, dict) or not isinstance(products.get("edges"), list):
+ raise RuntimeError("LOOKUP_HOLD: incomplete product lookup")
+ page = products.get("pageInfo")
+ if not isinstance(page, dict) or type(page.get("hasNextPage")) is not bool:
+ raise RuntimeError("LOOKUP_HOLD: missing pagination proof")
+ for e in products["edges"]:
n = e["node"]
p = _parse_node(n)
if any(v["sku"] == grs or v["sku"] == grs + "-Sample" for v in p["variants"]):
return p
+ # A candidate we are about to DISMISS must have been read in full; a hidden
+ # variant page could hold the very SKU we are looking for.
+ variant_page = (n.get("variants") or {}).get("pageInfo")
+ if (not isinstance(variant_page, dict)
+ or variant_page.get("hasNextPage") is not False):
+ raise RuntimeError("LOOKUP_HOLD: incomplete nonmatching variants")
+ if page["hasNextPage"]:
+ raise RuntimeError("LOOKUP_HOLD: truncated SKU search")
return None
+
+def require_no_prior_creation(grs, ledger_path=None):
+ """TK-11483 RECEIPT GUARD — the actual fix for the daily duplicate mint.
+
+ MEASURED MECHANISM: find_grs() resolves identity by SKU only. A competing writer
+ re-stamps a freshly created GRS-xxxxx sellable SKU to a DWFE-xxxx code, so the
+ next daily run's sku:GRS-xxxxx search legitimately finds nothing, concludes the
+ product is absent and creates it again — every single day. Evidence: 52 GRS codes
+ carry MORE THAN ONE create_grs_draft receipt (145 redundant creates), and 52 of
+ 52 products created on 2026-09-11 already had a prior receipt.
+
+ So identity is re-anchored on OUR OWN creation receipts, which no external SKU
+ rewrite can reach. If this GRS was ever created by this migration before, HOLD:
+ the row is skipped, nothing is created, and a human reconciles identity.
+
+ SCOPE, stated honestly: this is NOT global idempotency. A create that crashed
+ after productCreate but before its receipt was appended leaves no receipt and is
+ not covered, and neither is a concurrent second writer. It closes the measured,
+ reproducible daily-mint path, not every conceivable duplicate.
+
+ FAIL-CLOSED: an unreadable or malformed ledger cannot prove absence, so it HOLDs
+ rather than assuming a clean history.
+
+ `ledger_path` is the test seam (see test_receipt_guard.py). It is a function
+ argument only — never an env var and never a CLI flag — so no scheduled job can
+ accidentally point the guard at a fixture.
+ """
+ if not isinstance(grs, str) or not re.fullmatch(r"GRS-[0-9]+", grs):
+ raise RuntimeError("HISTORY_HOLD: invalid canonical GRS")
+ prior = set()
+ foreign_unparseable = 0
+ try:
+ with open(ledger_path or LEDGER, encoding="utf-8") as history:
+ for number, line in enumerate(history, 1):
+ if not line.strip():
+ continue
+ try:
+ entry = json.loads(line)
+ except ValueError:
+ # The executed-reversible ledger is a SHARED append-only file that
+ # many agents write; it demonstrably contains a few plain-text lines
+ # from other producers (measured 2026-09-11: 2 of 30,650). Holding on
+ # every one of those would brick this migration completely instead of
+ # blocking duplicates — a guard that always denies is not a guard.
+ # So the fail-closed rule is scoped to RELEVANCE, not to the whole
+ # file: an unparseable line that could plausibly be OUR receipt still
+ # HOLDs (we cannot prove absence from it); one that clearly belongs to
+ # another writer is counted and skipped.
+ if ("dwpw-grs-migrate" in line or "create_grs_draft" in line
+ or grs in line):
+ raise ValueError(
+ f"unparseable line {number} may be a dwpw-grs-migrate receipt")
+ foreign_unparseable += 1
+ continue
+ if not isinstance(entry, dict):
+ continue
+ if (entry.get("ticket") != "dwpw-grs-migrate"
+ or entry.get("action") != "create_grs_draft"):
+ continue
+ key = entry.get("grs")
+ restore = entry.get("restore_map")
+ pid = restore.get("product_id") if isinstance(restore, dict) else None
+ if (not isinstance(key, str) or not re.fullmatch(r"GRS-[0-9]+", key)
+ or not isinstance(pid, str)
+ or not re.fullmatch(r"gid://shopify/Product/[0-9]+", pid)
+ or entry.get("created_ids") != [pid]):
+ # A receipt that IS ours but is shaped wrong cannot establish
+ # absence. Unconditional HOLD.
+ raise ValueError(f"invalid creation receipt at line {number}")
+ if key == grs:
+ prior.add(pid)
+ except (OSError, UnicodeError, ValueError) as exc:
+ raise RuntimeError(
+ "HISTORY_HOLD: unavailable or malformed creation history") from exc
+ if prior:
+ raise RuntimeError(
+ f"HISTORY_HOLD: {grs} already has {len(prior)} recorded product(s) "
+ f"({', '.join(sorted(prior))}); reconcile identity before creating")
+
def find_grs_by_id(pid):
"""Re-read a product by its GID (read-your-writes consistent, unlike the SKU
search index which lags a fresh productCreate). Same shape as find_grs()."""
@@ -539,6 +645,10 @@ def process(row, apply):
plan = {"grs": grs, "mfr": row["mfr"], "title": row["title"]}
# ---- pre-flight reads (safe in dry-run) ----
existing = find_grs(grs)
+ if existing is None:
+ # TK-11483: SKU search says absent — but it said that yesterday too, and the
+ # day before. Check our own creation receipts before minting another one.
+ require_no_prior_creation(grs)
plan["ensure"] = "update" if existing else "create"
img_status = http_status(row["image"])
image_ok = (img_status == 200)
diff --git a/scripts/tests/test_receipt_guard.py b/scripts/tests/test_receipt_guard.py
new file mode 100644
index 0000000..ef6a8df
--- /dev/null
+++ b/scripts/tests/test_receipt_guard.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+"""TK-11483 — NEGATIVE TESTS for the dwpw-grs-migrate receipt guard.
+
+A positive-only test on a guard proves nothing (CLAUDE.md TK-11431 amendment 3).
+So the load-bearing case here is RED-BEFORE / GREEN-AFTER on the SAME injected
+fault: the exact daily-mint input is replayed through process() twice — once with
+the guard disabled (proving the fixture really does reproduce the duplicate mint,
+productCreate fires) and once with the guard live (proving it is the guard, and
+not the fixture, that stops it).
+
+Every test runs fully OFFLINE: gql is monkeypatched, a network tripwire replaces
+urllib.request.urlopen, and the ledger is a temp fixture. Zero network, zero cost,
+zero writes to the live store.
+
+Run: python3 scripts/tests/test_receipt_guard.py
+"""
+import importlib.util
+import json
+import os
+import sys
+import tempfile
+import traceback
+import urllib.request
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+SCRIPTS = os.path.dirname(HERE)
+MOD_PATH = os.path.join(SCRIPTS, "dwpw-grs-migrate.py")
+
+# ---------------------------------------------------------------- network tripwire
+def _no_network(*a, **k):
+ raise AssertionError("NETWORK TRIPWIRE: test attempted a real HTTP call")
+urllib.request.urlopen = _no_network
+
+os.environ.setdefault("SHOPIFY_FULL_ACCESS_TOKEN", "offline-test-token")
+spec = importlib.util.spec_from_file_location("dwpw_grs_migrate", MOD_PATH)
+M = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(M)
+
+PID = "gid://shopify/Product/7953141694515"
+GRS = "GRS-26830"
+
+def receipt(grs=GRS, pid=PID, ts="2026-09-10T16:22:35.741017Z"):
+ return {"action": "create_grs_draft", "grs": grs, "blast_radius": 1,
+ "restore_map": {"product_id": pid, "old_status": None, "new_status": "DRAFT"},
+ "created_ids": [pid], "undo_cmd": f"productDelete id={pid}",
+ "verify": f"find_grs({grs}) is None", "ts": ts,
+ "agent": "vp-dw-commerce", "ticket": "dwpw-grs-migrate"}
+
+def ledger_file(lines):
+ fh = tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False, encoding="utf-8")
+ for ln in lines:
+ fh.write((ln if isinstance(ln, str) else json.dumps(ln)) + "\n")
+ fh.close()
+ return fh.name
+
+ROW = {"grs": GRS, "mfr": "wnr1150", "title": "Avorio Sand Grasscloth Wallcovering",
+ "pattern": "Avorio Sand", "name": "Avorio Sand",
+ "image": "https://example.invalid/a.jpg",
+ "dw_price": "129.00", "cost_yd": 43.0}
+
+# ---------------------------------------------------------------- harness
+RESULTS = []
+def check(name, fn):
+ try:
+ fn(); RESULTS.append((True, name, "")); print(f" PASS {name}")
+ except Exception as e: # noqa: BLE001
+ RESULTS.append((False, name, f"{type(e).__name__}: {e}"))
+ print(f" FAIL {name}\n {type(e).__name__}: {e}")
+ traceback.print_exc(limit=2)
+
+def expect_raises(fragment, fn):
+ try:
+ fn()
+ except RuntimeError as e:
+ assert fragment in str(e), f"expected {fragment!r} in {e!r}"
+ return str(e)
+ raise AssertionError(f"expected RuntimeError containing {fragment!r}, nothing raised")
+
+# ================================================================ 1. THE NEGATIVE TEST
+# Same injected fault, twice. Guard OFF must mint (red), guard ON must refuse (green).
+class _MintReached(Exception):
+ """Raised by the mock the instant a productCreate mutation is issued."""
+
+def _replay(guard_on):
+ """Replay the measured daily-mint condition through the REAL process().
+
+ Injected fault = exactly what happens every morning at 16:22Z: a competing
+ writer has re-stamped this product's sellable SKU GRS-26830 -> DWFE-1478, so the
+ sku:GRS-26830 search legitimately returns an empty (and COMPLETE) result set,
+ while our own ledger holds a create receipt for GRS-26830 from yesterday.
+
+ Returns (reached_create: bool, hold_message: str|None).
+ """
+ reached = {"create": False}
+ def fake_gql(q, v=None):
+ if "mutation" in q and "productCreate" in q:
+ reached["create"] = True
+ raise _MintReached("productCreate issued")
+ if "mutation" in q:
+ return {"data": {}}
+ # complete, well-formed, genuinely empty SKU search
+ return {"data": {"products": {"pageInfo": {"hasNextPage": False}, "edges": []}}}
+ led = ledger_file([receipt()])
+ orig_gql, orig_http, orig_guard = M.gql, M.http_status, M.require_no_prior_creation
+ try:
+ M.gql = fake_gql
+ M.http_status = lambda url, **k: 200
+ if guard_on:
+ M.require_no_prior_creation = lambda g, ledger_path=None: orig_guard(g, led)
+ else:
+ M.require_no_prior_creation = lambda g, ledger_path=None: None # pre-fix
+ hold = None
+ try:
+ M.process(dict(ROW), True)
+ except _MintReached:
+ pass
+ except RuntimeError as e:
+ hold = str(e)
+ return reached["create"], hold
+ finally:
+ M.gql, M.http_status, M.require_no_prior_creation = orig_gql, orig_http, orig_guard
+ os.unlink(led)
+
+def t_red_before():
+ reached, hold = _replay(guard_on=False)
+ assert hold is None or "HISTORY_HOLD" not in hold, (
+ "guard-OFF replay must not hold - otherwise the fixture, not the guard, "
+ f"is what stops the mint (hold={hold!r})")
+ assert reached, "fixture failed to reproduce the duplicate mint: productCreate never fired"
+
+def t_green_after():
+ reached, hold = _replay(guard_on=True)
+ assert hold and "HISTORY_HOLD" in hold, f"guard did not hold; raised={hold!r}"
+ assert GRS in hold and PID in hold, f"hold message must name the collision: {hold!r}"
+ assert not reached, "guard raised but productCreate still fired"
+
+# ================================================================ 2. guard unit cases
+def t_prior_receipt_holds():
+ led = ledger_file([receipt()])
+ try:
+ msg = expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation(GRS, led))
+ assert "1 recorded product" in msg, msg
+ finally:
+ os.unlink(led)
+
+def t_counts_distinct_products():
+ led = ledger_file([receipt(pid="gid://shopify/Product/1"),
+ receipt(pid="gid://shopify/Product/2"),
+ receipt(pid="gid://shopify/Product/2")]) # dedup by product id
+ try:
+ msg = expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation(GRS, led))
+ assert "2 recorded product" in msg, msg
+ finally:
+ os.unlink(led)
+
+def t_no_prior_receipt_proceeds():
+ """POSITIVE CONTROL — without it, a guard that always denies would pass every
+ negative test above while breaking every legitimate first-time create."""
+ led = ledger_file([receipt(grs="GRS-99999")])
+ try:
+ M.require_no_prior_creation("GRS-26830", led) # must NOT raise
+ finally:
+ os.unlink(led)
+
+def t_ignores_other_producers():
+ r = receipt(); r["ticket"] = "some-other-job"
+ led = ledger_file([r])
+ try:
+ M.require_no_prior_creation(GRS, led) # not our receipt -> proceed
+ finally:
+ os.unlink(led)
+
+def t_ignores_non_create_actions():
+ r = receipt(); r["action"] = "publish_grs_active"
+ led = ledger_file([r])
+ try:
+ M.require_no_prior_creation(GRS, led)
+ finally:
+ os.unlink(led)
+
+def t_malformed_receipt_holds():
+ bad = receipt(); bad["created_ids"] = ["gid://shopify/Product/999"] # != restore_map pid
+ led = ledger_file([bad])
+ try:
+ expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation(GRS, led))
+ finally:
+ os.unlink(led)
+
+def t_corrupt_relevant_line_holds():
+ """An unparseable line that could be OUR receipt cannot prove absence -> HOLD."""
+ for frag in ('{"ticket": "dwpw-grs-migrate", "action": "create_grs',
+ '{"action": "create_grs_draft", trunc',
+ 'garbage mentioning GRS-26830 truncated'):
+ led = ledger_file([frag])
+ try:
+ expect_raises("HISTORY_HOLD",
+ lambda: M.require_no_prior_creation(GRS, led))
+ finally:
+ os.unlink(led)
+
+def t_corrupt_foreign_line_does_not_brick():
+ """REGRESSION GUARD on the candidate's real defect: the SHARED ledger contains
+ plain-text lines from other agents. Holding on those would make the migration a
+ total no-op every day — the candidate 7fe6f885 did exactly that against the real
+ file. A foreign corrupt line must be skipped, not fatal."""
+ led = ledger_file([
+ "[4AM-loop Tue Sep 8 04:04:51 PDT 2026] Auto-executing firm-phone-enrich",
+ "2026-09-09T22:41:03Z vp-dw-commerce TK-11331 vendor_registry mdc: stored creds",
+ receipt(grs="GRS-99999")])
+ try:
+ M.require_no_prior_creation(GRS, led) # must NOT raise
+ finally:
+ os.unlink(led)
+
+def t_corrupt_foreign_line_still_holds_on_real_prior():
+ """...and skipping foreign junk must not weaken the actual guard."""
+ led = ledger_file(["[4AM-loop] not json at all", receipt()])
+ try:
+ expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation(GRS, led))
+ finally:
+ os.unlink(led)
+
+def t_missing_ledger_holds():
+ expect_raises("HISTORY_HOLD",
+ lambda: M.require_no_prior_creation(GRS, "/nonexistent/ledger.jsonl"))
+
+def t_invalid_grs_holds():
+ led = ledger_file([])
+ try:
+ expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation("DWFE-1478", led))
+ expect_raises("HISTORY_HOLD", lambda: M.require_no_prior_creation(None, led))
+ finally:
+ os.unlink(led)
+
+# ================================================================ 3. find_grs fail-closed
+def _with_gql(resp):
+ def fake(q, v=None): return resp
+ orig = M.gql; M.gql = fake
+ try:
+ return M.find_grs(GRS)
+ finally:
+ M.gql = orig
+
+def t_findgrs_clean_absence_is_none():
+ """POSITIVE CONTROL for find_grs: a COMPLETE empty read still returns None."""
+ assert _with_gql({"data": {"products": {"pageInfo": {"hasNextPage": False},
+ "edges": []}}}) is None
+
+def t_findgrs_truncated_search_holds():
+ expect_raises("LOOKUP_HOLD", lambda: _with_gql(
+ {"data": {"products": {"pageInfo": {"hasNextPage": True}, "edges": []}}}))
+
+def t_findgrs_missing_pageinfo_holds():
+ expect_raises("LOOKUP_HOLD", lambda: _with_gql(
+ {"data": {"products": {"edges": []}}}))
+
+def t_findgrs_graphql_errors_holds():
+ expect_raises("LOOKUP_HOLD", lambda: _with_gql(
+ {"errors": [{"message": "Throttled"}],
+ "data": {"products": {"pageInfo": {"hasNextPage": False}, "edges": []}}}))
+
+def t_findgrs_hidden_variant_page_holds():
+ """A non-matching candidate whose variants were paginated away could be hiding
+ the very SKU we searched for — dismissing it would mint a duplicate."""
+ node = {"id": "gid://shopify/Product/1", "handle": "h", "status": "ACTIVE",
+ "featuredImage": None, "media": {"edges": []},
+ "variants": {"pageInfo": {"hasNextPage": True},
+ "edges": [{"node": {"id": "v1", "sku": "OTHER", "title": "Per Yard",
+ "price": "1.00", "position": 1,
+ "inventoryItem": {"tracked": True}}}]}}
+ expect_raises("LOOKUP_HOLD", lambda: _with_gql(
+ {"data": {"products": {"pageInfo": {"hasNextPage": False},
+ "edges": [{"node": node}]}}}))
+
+def t_findgrs_matching_product_returned():
+ node = {"id": PID, "handle": "h", "status": "ACTIVE", "featuredImage": None,
+ "media": {"edges": []},
+ "variants": {"pageInfo": {"hasNextPage": False},
+ "edges": [{"node": {"id": "v1", "sku": GRS, "title": "Per Yard",
+ "price": "1.00", "position": 1,
+ "inventoryItem": {"tracked": True}}}]}}
+ got = _with_gql({"data": {"products": {"pageInfo": {"hasNextPage": False},
+ "edges": [{"node": node}]}}})
+ assert got and got["id"] == PID, got
+
+# ================================================================ 4. real-history replay
+def t_real_ledger_would_have_held_today():
+ """Replay the REAL executed-reversible ledger: every GRS created on 2026-09-11
+ must be held by the guard, and a never-created GRS must not be."""
+ real = M.LEDGER
+ if not os.path.exists(real):
+ print(" (skipped: real ledger not present)"); return
+ today = []
+ with open(real, encoding="utf-8") as fh:
+ for ln in fh:
+ ln = ln.strip()
+ if not ln:
+ continue
+ try:
+ e = json.loads(ln)
+ except Exception: # noqa: BLE001
+ continue
+ if (e.get("ticket") == "dwpw-grs-migrate"
+ and e.get("action") == "create_grs_draft"
+ and str(e.get("ts", "")).startswith("2026-09-11")):
+ today.append(e["grs"])
+ assert today, "no 2026-09-11 creates found in the real ledger"
+ for grs in sorted(set(today)):
+ expect_raises("HISTORY_HOLD", lambda g=grs: M.require_no_prior_creation(g, real))
+ M.require_no_prior_creation("GRS-00000", real) # control: never created -> proceeds
+ print(f" (all {len(set(today))} GRS codes created 2026-09-11 would be HELD)")
+
+# ================================================================ 5. full CLI path
+def t_cli_main_holds_and_reports():
+ """END-TO-END through the REAL CLI entrypoint (argparse -> main -> per-row loop),
+ with the fault injected ONLY at the HTTP boundary, so nothing about the wiring is
+ mocked away. Proves three things at once: the guard is reachable from the path
+ launchd actually runs, a held row does NOT create, and the hold is VISIBLE in the
+ run output rather than silently counted as a no-op (the daily report's blind spot
+ is how this incident stayed invisible for four days).
+ """
+ import io, contextlib
+ created = {"n": 0}
+ def fake_gql(q, v=None):
+ if "mutation" in q:
+ created["n"] += 1
+ raise AssertionError("a mutation fired on a HELD row")
+ return {"data": {"products": {"pageInfo": {"hasNextPage": False}, "edges": []}}}
+ batch = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8")
+ json.dump([dict(ROW)], batch); batch.close()
+ orig_gql, orig_http, orig_argv = M.gql, M.http_status, sys.argv
+ buf = io.StringIO()
+ try:
+ M.gql = fake_gql
+ M.http_status = lambda url, **k: 200
+ # real ledger, real GRS-26830 receipts -> must HOLD
+ sys.argv = ["dwpw-grs-migrate.py", "--batch", batch.name, "--grs", GRS]
+ with contextlib.redirect_stdout(buf):
+ try:
+ M.main()
+ except SystemExit:
+ pass
+ finally:
+ M.gql, M.http_status, sys.argv = orig_gql, orig_http, orig_argv
+ os.unlink(batch.name)
+ out = buf.getvalue()
+ assert created["n"] == 0, "CLI issued a mutation on a held row"
+ assert "HISTORY_HOLD" in out, f"hold not surfaced in CLI output:\n{out[-1200:]}"
+ assert "ROW_ERROR" in out, f"held row not reported as an error row:\n{out[-1200:]}"
+ assert "would CREATE : 0" in out, f"CLI still planned a create:\n{out[-1200:]}"
+
+if __name__ == "__main__":
+ print("TK-11483 receipt-guard tests (offline, network tripwire armed)\n")
+ print(" NEGATIVE TEST — same injected fault, guard off vs guard on:")
+ check("RED before fix: guard disabled -> productCreate FIRES (dup minted)", t_red_before)
+ check("GREEN after fix: guard enabled -> HISTORY_HOLD, NO create", t_green_after)
+ print("\n guard unit cases:")
+ for n, f in [("prior receipt -> HOLD", t_prior_receipt_holds),
+ ("counts distinct product ids", t_counts_distinct_products),
+ ("CONTROL no prior receipt -> proceeds", t_no_prior_receipt_proceeds),
+ ("other producer's receipt ignored", t_ignores_other_producers),
+ ("non-create action ignored", t_ignores_non_create_actions),
+ ("malformed receipt -> HOLD", t_malformed_receipt_holds),
+ ("corrupt line that may be ours -> HOLD", t_corrupt_relevant_line_holds),
+ ("foreign corrupt line -> does NOT brick", t_corrupt_foreign_line_does_not_brick),
+ ("foreign junk + real prior -> still HOLD", t_corrupt_foreign_line_still_holds_on_real_prior),
+ ("missing ledger -> HOLD", t_missing_ledger_holds),
+ ("invalid GRS -> HOLD", t_invalid_grs_holds)]:
+ check(n, f)
+ print("\n find_grs fail-closed:")
+ for n, f in [("CONTROL complete empty read -> None", t_findgrs_clean_absence_is_none),
+ ("truncated SKU search -> LOOKUP_HOLD", t_findgrs_truncated_search_holds),
+ ("missing pageInfo -> LOOKUP_HOLD", t_findgrs_missing_pageinfo_holds),
+ ("graphql errors -> LOOKUP_HOLD", t_findgrs_graphql_errors_holds),
+ ("hidden variant page -> LOOKUP_HOLD", t_findgrs_hidden_variant_page_holds),
+ ("CONTROL matching product returned", t_findgrs_matching_product_returned)]:
+ check(n, f)
+ print("\n full CLI path:")
+ check("real CLI entrypoint holds, creates nothing, surfaces the hold",
+ t_cli_main_holds_and_reports)
+ print("\n real-history replay:")
+ check("every 2026-09-11 create would have been HELD", t_real_ledger_would_have_held_today)
+ bad = [r for r in RESULTS if not r[0]]
+ print(f"\n{len(RESULTS) - len(bad)}/{len(RESULTS)} passed")
+ sys.exit(1 if bad else 0)
← cbea880 TK-11461: selective gallery repair executor (dry-run default
·
back to Designerwallcoverings
·
TK-11483: skipped foreign ledger lines are reported, never s 67d7eb3 →