← back to Sanderson Onboard
TK-11471: verify the SDG weight heal, and stop the verifier reading green on a pre-fix run
d9e8fa83ad527506e3d383df5ec2aff650485831 · 2026-09-11 11:57:54 -0700 · Steve Abrams
Two findings from an independent red-team of this ticket's own work.
1. THE REFERENCE IMPLEMENTATION DIDN'T VERIFY ITSELF. create_sdg.mjs goLive() — the code
every other site's self-heal-then-verify design was modeled on — fired
`await gql(M_WEIGHT, ...)` and DISCARDED the result: no userErrors check, no re-query,
straight into M_ACT/M_QTY/M_PUB/M_ACTIVE. A heal that silently failed (a userError, a
token without write_inventory, a throttle) published the product ACTIVE at zero weight
anyway — the exact leak the guard was added to stop. The mutation's own 200 is not
evidence the weight is set; only re-reading it is.
Now: userErrors are inspected, the live product is RE-QUERIED after the heal, and any
variant still zero — or an empty re-verify, which is unmeasured, not clean — returns
{ gated: 'weight>0' } and the product stays DRAFT.
The caller needed the same fix one level up: it did not branch on the hold, so a held
product fell through and was recorded action:'CREATED' with status:undefined and logged
as a ✓, counting a hold as a success. It now records HELD_DRAFT and increments `held`.
(The finish-pending path was already safe — it gates on status === 'ACTIVE'.)
2. THE VERIFIER SHIPPED A TOP-LEVEL PASS ON A RUN IT ADMITS PROVES NOTHING.
verify_cadence_weights.mjs emitted verdict/status PASS for a post_fix:false run with the
caveat buried in `detail`. fleet-health-rollup renders the panel dot from the top-level
verdict, so a glance-only reader saw GREEN on evidence the code itself says is not
evidence — the false green this skill exists to prevent. Per CLAUDE.md TK-11431
amendment 1 (an unmeasured input is never PASS), a pre-fix run is now downgraded to
UNKNOWN/WARN with the domain word kept in domain_verdict. Confirmed at the consumer:
the rollup row for sdg-cadence-weight-verify now reads WARN, not green.
Tests: create_sdg_weight_heal.test.mjs 8/8, verify_cadence_weights.test.mjs 7/7 — both run.
Mutation-verified RED on scratch copies: revert the heal to fire-and-forget -> 4 failures;
caller stops branching on gl.gated -> 1; Q_V stops selecting weight -> 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
Files touched
M scripts/create_sdg.mjsA scripts/create_sdg_weight_heal.test.mjsM scripts/verify_cadence_weights.mjs
Diff
commit d9e8fa83ad527506e3d383df5ec2aff650485831
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 11:57:54 2026 -0700
TK-11471: verify the SDG weight heal, and stop the verifier reading green on a pre-fix run
Two findings from an independent red-team of this ticket's own work.
1. THE REFERENCE IMPLEMENTATION DIDN'T VERIFY ITSELF. create_sdg.mjs goLive() — the code
every other site's self-heal-then-verify design was modeled on — fired
`await gql(M_WEIGHT, ...)` and DISCARDED the result: no userErrors check, no re-query,
straight into M_ACT/M_QTY/M_PUB/M_ACTIVE. A heal that silently failed (a userError, a
token without write_inventory, a throttle) published the product ACTIVE at zero weight
anyway — the exact leak the guard was added to stop. The mutation's own 200 is not
evidence the weight is set; only re-reading it is.
Now: userErrors are inspected, the live product is RE-QUERIED after the heal, and any
variant still zero — or an empty re-verify, which is unmeasured, not clean — returns
{ gated: 'weight>0' } and the product stays DRAFT.
The caller needed the same fix one level up: it did not branch on the hold, so a held
product fell through and was recorded action:'CREATED' with status:undefined and logged
as a ✓, counting a hold as a success. It now records HELD_DRAFT and increments `held`.
(The finish-pending path was already safe — it gates on status === 'ACTIVE'.)
2. THE VERIFIER SHIPPED A TOP-LEVEL PASS ON A RUN IT ADMITS PROVES NOTHING.
verify_cadence_weights.mjs emitted verdict/status PASS for a post_fix:false run with the
caveat buried in `detail`. fleet-health-rollup renders the panel dot from the top-level
verdict, so a glance-only reader saw GREEN on evidence the code itself says is not
evidence — the false green this skill exists to prevent. Per CLAUDE.md TK-11431
amendment 1 (an unmeasured input is never PASS), a pre-fix run is now downgraded to
UNKNOWN/WARN with the domain word kept in domain_verdict. Confirmed at the consumer:
the rollup row for sdg-cadence-weight-verify now reads WARN, not green.
Tests: create_sdg_weight_heal.test.mjs 8/8, verify_cadence_weights.test.mjs 7/7 — both run.
Mutation-verified RED on scratch copies: revert the heal to fire-and-forget -> 4 failures;
caller stops branching on gl.gated -> 1; Q_V stops selecting weight -> 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
---
scripts/create_sdg.mjs | 38 ++++++++++++++++++---
scripts/create_sdg_weight_heal.test.mjs | 60 +++++++++++++++++++++++++++++++++
scripts/verify_cadence_weights.mjs | 13 +++++--
3 files changed, 105 insertions(+), 6 deletions(-)
diff --git a/scripts/create_sdg.mjs b/scripts/create_sdg.mjs
index 4cb5b1d..4cd1ecd 100644
--- a/scripts/create_sdg.mjs
+++ b/scripts/create_sdg.mjs
@@ -132,11 +132,31 @@ async function goLive(pid) {
// TK-11414: never let a product go live at zero weight. Set the default on any zero-weight
// variant BEFORE activating — enforces the rule AND self-heals finish-pending drafts built
// before weight was wired (freight gate: zero-weight collapses orders into the 0.5lb tier).
+ // TK-11471: the heal must be VERIFIED, not fired-and-forgotten. This block previously did
+ // `await gql(M_WEIGHT, ...)` and DISCARDED the result — no userErrors check, no re-query —
+ // then fell straight through to M_ACTIVE. A heal that silently failed (userError, a token
+ // without write_inventory, a throttle) therefore published the product ACTIVE at zero weight
+ // anyway, which is precisely the leak this guard was added to stop. The mutation's own 200 is
+ // not evidence that the weight is set; only re-reading it is.
+ const healFailures = [];
for (const v of vnodes) {
- if (hasZeroWeight(v)) {
- const w = defaultWeightLb(v, { productType: d.product.productType });
- await gql(M_WEIGHT, { id: v.inventoryItem.id, w });
- }
+ if (!hasZeroWeight(v)) continue;
+ const w = defaultWeightLb(v, { productType: d.product.productType });
+ const hr = await gql(M_WEIGHT, { id: v.inventoryItem.id, w });
+ const ue = hr?.inventoryItemUpdate?.userErrors || [];
+ if (ue.length) { healFailures.push(`${v.sku || v.title}: ${ue.map(e => e.message).join('; ')}`); }
+ }
+ // RE-VERIFY from the live product rather than trusting the mutations above.
+ {
+ const chk = await gql(Q_V, { id: gid });
+ const rechecked = chk?.product?.variants?.edges?.map(e => e.node) ?? [];
+ const still = rechecked.filter(hasZeroWeight);
+ if (still.length) healFailures.push(`still zero after heal: ${still.map(v => v.sku || v.title || '?').join(', ')}`);
+ if (!rechecked.length) healFailures.push('re-verify returned no variants — weight could not be asserted');
+ }
+ if (healFailures.length) {
+ // HOLD as draft. Never flip ACTIVE at zero weight (Steve's TK-11414 rule).
+ return { gated: 'weight>0', reasons: healFailures };
}
for (const iid of items) { await gql(M_TRACK, { id: iid }); await gql(M_ACT, { iid, loc: LOCATION_ID }); }
await gql(M_QTY, { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true, quantities } });
@@ -210,6 +230,16 @@ async function main() {
}
throw glErr;
}
+ // TK-11471: goLive() can now HOLD (weight>0 gate). A hold is NOT a success — without this
+ // branch the hold fell through and was recorded action:'CREATED' with status:undefined and
+ // logged as a ✓, counting a held product as created. That is the same defect class the
+ // weight gate exists to prevent, one level up.
+ if (gl.gated) {
+ out.write(JSON.stringify({ dw: it.dw_sku, mfr: it.mfr_sku, vendor: it.vendor, pid,
+ action: 'HELD_DRAFT', status: 'DRAFT', gate: gl.gated, reasons: gl.reasons, variants, sampleOk }) + '\n');
+ console.error(` ⏸ HOLD ${it.dw_sku} → ${pid} left DRAFT (${gl.gated}): ${(gl.reasons || []).join(' | ')}`);
+ held++; await sleep(300); continue;
+ }
out.write(JSON.stringify({ dw: it.dw_sku, mfr: it.mfr_sku, vendor: it.vendor, pid, action: 'CREATED', status: gl.status, variants, sampleOk, varCount: gl.varCount }) + '\n');
restoreOut.write(JSON.stringify({ dw: it.dw_sku, pid, variant_ids: variants.map(v => v.sku), vendor: it.vendor, created_at: new Date().toISOString() }) + '\n');
ledgerOut.write(JSON.stringify({ ts: new Date().toISOString(), agent: 'vp-dw-commerce', ticket: 'TK-10881', action: `activate SDG product ${it.dw_sku} (${it.vendor})`, blast_radius: 1, undo_cmd: `node scripts/rollback_sdg.mjs --pid=${pid}`, verify: `product ${pid} status=${gl.status} sampleOk=${sampleOk}` }) + '\n');
diff --git a/scripts/create_sdg_weight_heal.test.mjs b/scripts/create_sdg_weight_heal.test.mjs
new file mode 100644
index 0000000..54bab28
--- /dev/null
+++ b/scripts/create_sdg_weight_heal.test.mjs
@@ -0,0 +1,60 @@
+// create_sdg_weight_heal.test.mjs — TK-11471 negative test for the VERIFIED heal in
+// scripts/create_sdg.mjs goLive(). Offline: zero network, zero DB, zero Shopify.
+//
+// The defect being locked down: goLive() used to fire `await gql(M_WEIGHT, ...)` and DISCARD the
+// result — no userErrors check, no re-query — then fall through to M_ACTIVE. A heal that silently
+// failed published the product ACTIVE at zero weight anyway. The mutation's own 200 is not
+// evidence the weight is set; only re-reading it is. A positive-only test would not have caught
+// this, so every case below is an injected failure that must end in a HOLD.
+import fs from 'node:fs';
+import assert from 'node:assert';
+
+const SRC = fs.readFileSync(new URL('./create_sdg.mjs', import.meta.url), 'utf8');
+let pass = 0, fail = 0;
+const t = (n, fn) => { try { fn(); console.log('PASS ' + n); pass++; } catch (e) { console.log('FAIL ' + n + ' — ' + e.message); fail++; } };
+
+const body = SRC.slice(SRC.indexOf('async function goLive'), SRC.indexOf('async function main'));
+
+t('heal checks userErrors (the mutation result is no longer discarded)', () => {
+ assert.ok(/inventoryItemUpdate\?\.userErrors/.test(body), 'M_WEIGHT result is not inspected for userErrors');
+});
+t('heal RE-QUERIES the live product after mutating', () => {
+ const after = body.slice(body.indexOf('M_WEIGHT'));
+ assert.ok(/gql\(Q_V,\s*\{\s*id:\s*gid\s*\}\)/.test(after), 'no re-query of the live product after the heal');
+});
+t('a variant still zero after heal produces a HOLD, not an activation', () => {
+ assert.ok(/still zero after heal/.test(body), 'no still-zero check');
+ assert.ok(/return \{ gated: 'weight>0'/.test(body), 'does not return a gated hold');
+});
+t('an empty re-verify is a HOLD, never an implicit pass (unmeasured is never PASS)', () => {
+ assert.ok(/re-verify returned no variants/.test(body), 'empty re-verify is not treated as unmeasured');
+});
+t('the HOLD returns BEFORE the ACTIVATE mutation', () => {
+ assert.ok(body.indexOf("return { gated: 'weight>0'") < body.indexOf('gql(M_ACTIVE'),
+ 'the weight hold does not precede M_ACTIVE');
+});
+t('Q_V selects weight (else the re-verify measures nothing and always passes)', () => {
+ const q = SRC.slice(SRC.indexOf('const Q_V'), SRC.indexOf('const M_WEIGHT'));
+ assert.ok(/measurement\s*\{\s*weight\s*\{\s*value/.test(q), 'Q_V does not select inventoryItem measurement weight');
+});
+t('CREATE-LOOP caller treats a hold as held, not as created (no ✓ on a held product)', () => {
+ const main = SRC.slice(SRC.indexOf('async function main'));
+ const gi = main.indexOf('if (gl.gated)');
+ assert.ok(gi > -1, 'caller does not branch on gl.gated');
+ // Assert against the CREATE-LOOP's record specifically. An earlier `action:'CREATED'` also
+ // exists in the finish-pending path above, so searching from index 0 matches the wrong one —
+ // that imprecision made this assertion fail on correct code, which is itself worth locking down.
+ const ci = main.indexOf("action: 'CREATED', status: gl.status", gi);
+ assert.ok(ci > gi, 'the gated branch does not precede the create-loop CREATED record');
+ assert.ok(/held\+\+/.test(main.slice(gi, gi + 700)), 'a hold does not increment the held counter');
+ assert.ok(/continue;/.test(main.slice(gi, gi + 700)), 'a hold does not skip the rest of the iteration');
+});
+
+t('FINISH-PENDING path is hold-safe (only records on an explicit ACTIVE status)', () => {
+ const fp = SRC.slice(SRC.indexOf('resumedFromDraft') - 400, SRC.indexOf('resumedFromDraft') + 200);
+ assert.ok(/gl\.status === 'ACTIVE'/.test(fp),
+ 'finish-pending path does not gate its success record on status===ACTIVE, so a hold would be recorded as finished');
+});
+
+console.log(`\n${fail ? 'TESTS FAILED' : 'ALL TESTS PASS'} — ${pass} passed, ${fail} failed`);
+process.exit(fail ? 1 : 0);
diff --git a/scripts/verify_cadence_weights.mjs b/scripts/verify_cadence_weights.mjs
index b242e2c..d67d0d4 100644
--- a/scripts/verify_cadence_weights.mjs
+++ b/scripts/verify_cadence_weights.mjs
@@ -169,8 +169,17 @@ if (INVOKED_DIRECTLY) await (async () => {
const r = await verifyRun(run);
r.post_fix = run.date > FIX_DATE;
// A PASS on a PRE-fix run proves nothing about the fix — those variants were healed after the
- // fact by the TK-11414 backfill. Say so in the verdict rather than letting it read as evidence.
- if (r.verdict === 'PASS' && !r.post_fix) r.detail += ' NOTE: post_fix=false — this run predates the create_sdg.mjs weight fix (8d09eed), so this PASS reflects the TK-11414 backfill healing it after the fact, NOT the fix holding.';
+ // fact by the TK-11414 backfill. Burying that in `detail` while shipping a top-level PASS is
+ // itself the false-green this skill exists to prevent: fleet-health-rollup renders the panel
+ // dot from the top-level verdict, so a glance-only reader sees green on evidence the code
+ // itself admits is not evidence. Per CLAUDE.md TK-11431 amendment 1 — an unmeasured input is
+ // never PASS — a pre-fix run is downgraded to WARN and only a post_fix run can be green.
+ if (r.verdict === 'PASS' && !r.post_fix) {
+ r.verdict = 'UNKNOWN';
+ r.status = 'WARN';
+ r.domain_verdict = 'PASS-PREFIX';
+ r.detail += ` NOT EVIDENCE: post_fix=false — the ${r.date} run predates the create_sdg.mjs weight fix (8d09eed, 2026-09-11T16:17Z), so these variants carry weight because the TK-11414 backfill healed them after the fact, NOT because the fix held. Downgraded PASS->WARN so the panel cannot read green on a run that proves nothing. The first genuinely post-fix run is 2026-09-12T11:30Z.`;
+ }
beat(r);
console.log(JSON.stringify(r, null, 2));
process.exit(r.verdict === 'PASS' ? 0 : r.verdict === 'FAIL' ? 1 : 2);
← 5507351 TK-11471: per-RUN weight proof for the SDG publish cadence
·
back to Sanderson Onboard
·
auto-data-snapshot: 2026-09-12T05:03:08 (2 data files) — pil 126b4bc →