← back to Dw Sku Integrity
TK-10900/A: graceful schema-gate + bucket_guard flag (sweep-safe)
9aea723773d5fc1db53e76c3fd924574f8cbb8c9 · 2026-08-31 02:29:48 -0700 · codex-10896
Fleet sweep revealed catalogs are heterogeneous: color_name (most), title-only
(Vahallan), no product_type (Fentucci). Tool now DEFERS unsupported schemas with
a clean JSON status instead of crashing, and flags bucket_guard=false when a
catalog lacks product_type (cross-class guard can't run). Finding: the numeric
color_number parse mode only covers Carnegie (2,745); every color_name vendor
100%-misses -> needs an A2 color-name/title matcher (separate build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 9aea723773d5fc1db53e76c3fd924574f8cbb8c9
Author: codex-10896 <steve@designerwallcoverings.com>
Date: Mon Aug 31 02:29:48 2026 -0700
TK-10900/A: graceful schema-gate + bucket_guard flag (sweep-safe)
Fleet sweep revealed catalogs are heterogeneous: color_name (most), title-only
(Vahallan), no product_type (Fentucci). Tool now DEFERS unsupported schemas with
a clean JSON status instead of crashing, and flags bucket_guard=false when a
catalog lacks product_type (cross-class guard can't run). Finding: the numeric
color_number parse mode only covers Carnegie (2,745); every color_name vendor
100%-misses -> needs an A2 color-name/title matcher (separate build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
content-match-gen.mjs | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/content-match-gen.mjs b/content-match-gen.mjs
index c04a4c9..1b4b26e 100644
--- a/content-match-gen.mjs
+++ b/content-match-gen.mjs
@@ -63,19 +63,30 @@ const LOCAL = ['psql', '-h', '/tmp', '-d', 'dw_unified'];
const catalog = CATALOG || `${VENDOR.toLowerCase().replace(/\s+/g, '_')}_catalog`;
const cols = new Set(runPsql(LOCAL,
`SELECT column_name FROM information_schema.columns WHERE table_name='${sqlEscape(catalog)}';`).map((r) => r[0]));
-for (const need of ['dw_sku', 'mfr_sku', 'pattern_name', 'product_type']) {
- if (!cols.has(need)) { console.error(`FATAL: ${catalog} lacks required column '${need}'. Columns: ${[...cols].join(', ')}`); process.exit(2); }
-}
-const colColumn = cols.has('color_number') ? 'color_number' : (cols.has('color_name') ? 'color_name' : null);
-if (!colColumn) { console.error(`FATAL: ${catalog} has neither color_number nor color_name.`); process.exit(2); }
const useMfr = MINT_CATALOG.has(VENDOR.toLowerCase());
+const colColumn = cols.has('color_number') ? 'color_number' : (cols.has('color_name') ? 'color_name' : null);
+const hasProductType = cols.has('product_type');
+
+// Graceful schema gate: this tool implements the pattern+color match mode. Catalogs that don't fit
+// (no code source, or no pattern_name+color) are DEFERRED to a fitted matcher (see the work-plan),
+// reported cleanly rather than crashed, so a fleet sweep stays informative.
+function deferUnsupported(reason, detail) {
+ const s = { ticket: 'TK-10900', program: 'A-content-match', vendor: VENDOR, catalog, status: 'deferred_unsupported_schema', reason, detail, columns: [...cols], matched: 0, statements: 0, note: 'NOTHING executed. Needs a schema-fitted matcher (color-name / title-match mode).' };
+ writeFileSync(join(OUT, `SUMMARY-${VENDOR}.json`), JSON.stringify(s, null, 2) + '\n');
+ console.log(JSON.stringify(s, null, 2));
+ process.exit(0);
+}
+mkdirSync(OUT, { recursive: true });
+const codeCol = useMfr ? 'mfr_sku' : 'dw_sku';
+if (!cols.has(codeCol)) deferUnsupported('no_code_source', `needs '${codeCol}' (${useMfr ? 'mint-catalog vendor' : 'clean-catalog vendor'})`);
+if (!cols.has('pattern_name') || !colColumn) deferUnsupported('no_pattern_color_key', `pattern+color mode needs pattern_name + color_number|color_name (has color? ${colColumn || 'none'})`);
// 1. Reverted-mint exclude list.
const minted = new Set(existsSync(LEDGER) ? readFileSync(LEDGER, 'utf8').split('\n').map((s) => s.trim().toUpperCase()).filter(Boolean) : []);
// 2. Build the catalog map: key "(pattern|color)" -> Map(baseCode -> Set(bucket)).
const catRows = runPsql(LOCAL,
- `SELECT coalesce(pattern_name,''), coalesce(${colColumn},''), coalesce(product_type,''), ` +
+ `SELECT coalesce(pattern_name,''), coalesce(${colColumn},''), ${hasProductType ? 'coalesce(product_type,\'\')' : "''"}, ` +
`coalesce(dw_sku,''), coalesce(mfr_sku,'') FROM ${catalog};`);
const map = new Map();
let catUsable = 0, catExcluded = 0;
@@ -150,8 +161,8 @@ writeFileSync(join(dir, 'restore-map.json'), JSON.stringify(restoreMap, null, 2)
writeFileSync(join(dir, 'review-queue.json'), JSON.stringify(review, null, 2) + '\n');
const summary = {
- ticket: 'TK-10900', program: 'A-content-match', vendor: VENDOR, catalog, color_column: colColumn,
- code_source: useMfr ? 'mfr_sku(base-stripped)' : 'dw_sku', catalog_usable: catUsable, catalog_excluded_mint: catExcluded,
+ ticket: 'TK-10900', program: 'A-content-match', vendor: VENDOR, catalog, status: 'supported', color_column: colColumn,
+ code_source: useMfr ? 'mfr_sku(base-stripped)' : 'dw_sku', bucket_guard: hasProductType, catalog_usable: catUsable, catalog_excluded_mint: catExcluded,
...stat, review_queue: review.length, statements: entries.length,
match_pct: stat.shopify_blank ? (100 * stat.matched / stat.shopify_blank).toFixed(1) + '%' : '0%',
note: 'NOTHING executed. apply.sql is a GATED draft; review-queue.json is NOT written.',
← e5896e1 TK-10900: plan evidence updated — content-match SHIPPED + co
·
back to Dw Sku Integrity
·
TK-10900: plan corrected — Program A splits A1(numeric,shipp 8139758 →