← back to Gmc Titlefix
gmc mdc-title: fix 404 — bare-variant offerId, en~US~ name, reuse linked datasource (TK-11307)
8f147e652e773afe7abc4292eb5587f937ee8286 · 2026-09-09 08:22:17 -0700 · steve
Root cause of the live 404 'Product not found': (1) MDC offers are on the LEGACY
bare-variant-id GMC feed (offerId = bare Shopify variant id, name en~US~<offerId>),
not the compound shopify_US_<pid>_<vid>/online~ format the pusher constructed; (2)
step-a created NEW unbound supplemental datasources (the two empty dupes) instead of
reusing the existing primary-LINKED 'DW Sample Title Overrides' (10677010255).
Fix: builder now pulls the real GMC offerId set and includes ONLY MDC offers that
actually exist on GMC (offerId = bare sample-variant legacyResourceId) — the push can
never 404 on a missing product. resolveSampleTitleDS() reuses the single existing
datasource referenced by a primary's takeFromDataSources (idempotent — never creates a
dupe). Validated by a SINGLE-OFFER live insert (offer 44595192496179 'Lisbon Hard Rock',
HTTP 200; supplemental confirmed linked to primary 180695450). 13/13 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr
Files touched
M build-mdc-sample-titles.mjsM push-mdc-sample-titles.mjsM test-mdc-sample-titles.mjs
Diff
commit 8f147e652e773afe7abc4292eb5587f937ee8286
Author: steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 08:22:17 2026 -0700
gmc mdc-title: fix 404 — bare-variant offerId, en~US~ name, reuse linked datasource (TK-11307)
Root cause of the live 404 'Product not found': (1) MDC offers are on the LEGACY
bare-variant-id GMC feed (offerId = bare Shopify variant id, name en~US~<offerId>),
not the compound shopify_US_<pid>_<vid>/online~ format the pusher constructed; (2)
step-a created NEW unbound supplemental datasources (the two empty dupes) instead of
reusing the existing primary-LINKED 'DW Sample Title Overrides' (10677010255).
Fix: builder now pulls the real GMC offerId set and includes ONLY MDC offers that
actually exist on GMC (offerId = bare sample-variant legacyResourceId) — the push can
never 404 on a missing product. resolveSampleTitleDS() reuses the single existing
datasource referenced by a primary's takeFromDataSources (idempotent — never creates a
dupe). Validated by a SINGLE-OFFER live insert (offer 44595192496179 'Lisbon Hard Rock',
HTTP 200; supplemental confirmed linked to primary 180695450). 13/13 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr
---
build-mdc-sample-titles.mjs | 123 ++++++++++++++++++++++++++++----------------
push-mdc-sample-titles.mjs | 56 ++++++++++----------
test-mdc-sample-titles.mjs | 9 ++++
3 files changed, 117 insertions(+), 71 deletions(-)
diff --git a/build-mdc-sample-titles.mjs b/build-mdc-sample-titles.mjs
index f112490..5d5b4ac 100644
--- a/build-mdc-sample-titles.mjs
+++ b/build-mdc-sample-titles.mjs
@@ -5,20 +5,23 @@
*
* WHY: MDC products carry a single $4.25 memo-sample variant (no roll). On Google the
* OFFER PRICE is a compliant $4.25, but the TITLE reads "<Pattern> Wallcovering | Phillipe
- * Romano" — so Google sees a $4.25-priced wallcovering (a ~$300 item at $4.25) and flags a
- * price-mismatch. Per Google's sample policy the title must BEGIN with "Sample". This tool
- * builds a supplemental-feed title-override list (offerId -> "Sample — …") that changes ONLY
- * the GOOGLE-facing title via a GMC supplemental data source — the on-site Shopify PDP title
- * is NEVER touched (shoppers still see the wallcovering name).
+ * Romano" — so Google sees a $4.25-priced wallcovering and flags a price-mismatch. Per
+ * Google's sample policy the title must BEGIN with "Sample". This builds a supplemental-feed
+ * title-override list that changes ONLY the GOOGLE title via a GMC supplemental data source
+ * — the on-site Shopify PDP title is NEVER touched.
*
- * SCOPE — STRICTLY tag:Showroom, ACTIVE, actually published to the Google & YouTube channel.
- * Belt-and-suspenders: re-affirms hasShowroomTag per product, so a sellable Phillipe Romano
- * product (Spazzolato etc., no Showroom tag) is NEVER in the list and its title is unchanged.
+ * OFFER IDENTITY (fixed 2026-09-09 after a 404 diagnosis): MDC offers live on GMC under the
+ * LEGACY BARE-VARIANT-ID feed — offerId = the bare Shopify variant legacyResourceId (e.g.
+ * "44595192496179"), product resource name = "en~US~<offerId>" (NO "online~" prefix, NO
+ * "shopify_US_" compound). Not every ACTIVE tag:Showroom product is actually a live GMC
+ * product, so this builder pulls the REAL set of GMC offerIds and includes ONLY MDC offers
+ * that already exist on GMC — so the push can never 404 on a missing product.
*
- * READ-ONLY against Shopify (query only). Writes one local JSON. Nothing is pushed to GMC
- * here — push-mdc-sample-titles.mjs (Steve-gated) does that.
+ * SCOPE — STRICTLY tag:Showroom, ACTIVE. Belt-and-suspenders: re-affirms hasShowroomTag per
+ * product, so a sellable Phillipe Romano product (Spazzolato etc.) is never included.
*
- * OUTPUT: data/mdc-sample-title-overrides.json (rows: offerId, gmcId, productId, currentTitle, proposedTitle)
+ * READ-ONLY (Shopify GET + GMC products.list GET). Writes one local JSON. Nothing pushed.
+ * OUTPUT: data/mdc-sample-title-overrides.json (offerId, gmcName, productId, sku, currentTitle, proposedTitle)
* USAGE: node build-mdc-sample-titles.mjs
*/
import fs from 'node:fs';
@@ -27,17 +30,17 @@ import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { hasShowroomTag } = require(process.env.HOME + '/Projects/fix-live-board/config/showroom-vendor.cjs');
+const { token, MERCHANT } = require('./_auth');
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SHOP = 'designer-laboratory-sandbox.myshopify.com';
const VER = '2024-10';
-const GOOGLE_PUBLICATION = 'gid://shopify/Publication/29646651457'; // Google & YouTube
const GTITLE_MAX = 150; // Google title max length
/**
* The Google-facing sample title transform (exported for tests). Strips a trailing
- * "| Phillipe Romano" / "- Phillipe Romano" vendor suffix, wraps as a memo sample, and
- * ALWAYS begins with "Sample". Truncation is end-only so the "Sample" prefix survives.
+ * "| Phillipe Romano" vendor suffix, wraps as a memo sample, ALWAYS begins with "Sample".
+ * Truncation is end-only so the "Sample" prefix survives.
*/
export function mdcSampleTitle(title) {
const clean = String(title == null ? '' : title)
@@ -54,53 +57,83 @@ export function sampleVariant(variants) {
return variants.find(v => /-sample$/i.test(v.sku || '')) || variants[0];
}
-// main (skipped when imported for tests)
+/**
+ * Resolve the EXISTING, PRIMARY-LINKED "DW Sample Title Overrides" supplemental data source
+ * (never create a new one — that spawned the empty-duplicate 404 mess). Returns the data
+ * source RESOURCE NAME that is actually referenced by a primary feed's takeFromDataSources.
+ * (exported so the pusher reuses the identical resolution.)
+ */
+export async function resolveSampleTitleDS(tok) {
+ const rd = await fetch(`https://merchantapi.googleapis.com/datasources/v1/accounts/${MERCHANT}/dataSources`, { headers: { Authorization: 'Bearer ' + tok } });
+ const jd = await rd.json();
+ const all = jd.dataSources || [];
+ // candidate supplementals named "DW Sample Title Overrides"
+ const named = all.filter(d => d.supplementalProductDataSource && /DW Sample Title Overrides/i.test(d.displayName || ''));
+ // linked set = every supplemental referenced by any primary's defaultRule.takeFromDataSources
+ const linked = new Set();
+ for (const d of all) {
+ const takes = d.primaryProductDataSource?.defaultRule?.takeFromDataSources || [];
+ for (const t of takes) if (t.supplementalDataSourceName) linked.add(t.supplementalDataSourceName);
+ }
+ const chosen = named.find(d => linked.has(d.name)) || named[0];
+ if (!chosen) throw new Error('no "DW Sample Title Overrides" supplemental data source found — create ONE and link it to the primary first');
+ return { name: chosen.name, linked: linked.has(chosen.name), duplicates: named.filter(d => d.name !== chosen.name).map(d => d.name) };
+}
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
const TOKEN = (fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8')
.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
- if (!TOKEN) { console.error('no token'); process.exit(1); }
- const ENDPOINT = `https://${SHOP}/admin/api/${VER}/graphql.json`;
+ if (!TOKEN) { console.error('no Shopify token'); process.exit(1); }
+ const SEP = `https://${SHOP}/admin/api/${VER}/graphql.json`;
const sleep = ms => new Promise(r => setTimeout(r, ms));
- const gql = async (query, variables) => {
+ const sgql = async (query, variables) => {
for (let a = 0; a < 8; a++) {
let j;
- try {
- const r = await fetch(ENDPOINT, { method: 'POST',
- headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
- body: JSON.stringify({ query, variables }) });
- j = await r.json();
- } catch { await sleep(1500 * (a + 1)); continue; }
+ try { const r = await fetch(SEP, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }) }); j = await r.json(); }
+ catch { await sleep(1500 * (a + 1)); continue; }
if (j.errors) { if (JSON.stringify(j.errors).includes('THROTTLED')) { await sleep(2000 * (a + 1)); continue; } throw new Error(JSON.stringify(j.errors)); }
const t = j.extensions?.cost?.throttleStatus; if (t && t.currentlyAvailable < 400) await sleep(1200);
return j.data;
}
- throw new Error('retries');
+ throw new Error('shopify retries');
};
- const Q = `query($cursor:String){
- products(first:100, after:$cursor, query:"status:active tag:'Showroom'"){
- pageInfo{ hasNextPage endCursor }
- nodes{ legacyResourceId title tags
- onGoogle:publishedOnPublication(publicationId:"${GOOGLE_PUBLICATION}")
- variants(first:10){ nodes{ legacyResourceId sku } } }
- }
- }`;
-
(async () => {
- const rows = []; let cursor = null, has = true, seen = 0, offCh = 0, notag = 0;
+ const tok = await token();
+ // 1. Real GMC offerId set (paginate products.list) — so we only override offers that EXIST.
+ process.stderr.write('pulling GMC offerId set…\n');
+ const gmcOffers = new Set();
+ let pt = null, gpages = 0;
+ do {
+ const url = `https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/products?pageSize=250` + (pt ? `&pageToken=${pt}` : '');
+ const r = await fetch(url, { headers: { Authorization: 'Bearer ' + tok } });
+ const j = await r.json();
+ for (const p of (j.products || [])) if (p.offerId) gmcOffers.add(String(p.offerId));
+ pt = j.nextPageToken; gpages++;
+ } while (pt && gpages < 400);
+ process.stderr.write(` GMC offers: ${gmcOffers.size} (pages ${gpages})\n`);
+
+ // 2. Enumerate ACTIVE tag:Showroom MDC products; offerId = bare sample-variant legacyId.
+ const Q = `query($cursor:String){
+ products(first:100, after:$cursor, query:"status:active tag:'Showroom'"){
+ pageInfo{ hasNextPage endCursor }
+ nodes{ legacyResourceId title tags variants(first:10){ nodes{ legacyResourceId sku } } }
+ }
+ }`;
+ const rows = []; let cursor = null, has = true, seen = 0, notag = 0, notOnGmc = 0;
while (has) {
- const d = await gql(Q, { cursor });
+ const d = await sgql(Q, { cursor });
for (const p of d.products.nodes) {
seen++;
- if (!hasShowroomTag(p.tags)) { notag++; continue; } // re-affirm the tag — never trust the query
- if (!p.onGoogle) { offCh++; continue; } // only offers actually on Google
+ if (!hasShowroomTag(p.tags)) { notag++; continue; }
const v = sampleVariant(p.variants.nodes);
if (!v) continue;
- const offerId = `shopify_US_${p.legacyResourceId}_${v.legacyResourceId}`;
+ const offerId = String(v.legacyResourceId); // BARE variant id — the legacy GMC feed identity
+ if (!gmcOffers.has(offerId)) { notOnGmc++; continue; } // not a live GMC product → skip (no 404)
rows.push({
offerId,
- gmcId: `online:en:US:${offerId}`,
+ gmcName: `accounts/${MERCHANT}/products/en~US~${offerId}`,
productId: String(p.legacyResourceId),
sku: v.sku,
currentTitle: p.title,
@@ -109,14 +142,18 @@ if (isMain) {
}
has = d.products.pageInfo.hasNextPage; cursor = d.products.pageInfo.endCursor;
}
+
+ const ds = await resolveSampleTitleDS(tok).catch(e => ({ error: e.message }));
const DIR = path.join(__dirname, 'data'); fs.mkdirSync(DIR, { recursive: true });
const out = path.join(DIR, 'mdc-sample-title-overrides.json');
fs.writeFileSync(out, JSON.stringify(rows, null, 1));
console.log('MDC (tag:Showroom) Google sample-title override list');
- console.log(` active tag:'Showroom' seen: ${seen} | skipped(no real tag): ${notag} | skipped(off Google): ${offCh}`);
+ console.log(` active tag:'Showroom' seen: ${seen} | skipped(no real tag): ${notag} | skipped(not a live GMC offer): ${notOnGmc}`);
console.log(` overrides written: ${rows.length}`);
- if (rows[0]) { console.log(' sample row:'); console.log(' ', rows[0].offerId); console.log(' ', rows[0].currentTitle, '->', rows[0].proposedTitle); }
+ if (rows[0]) { console.log(' sample:', rows[0].offerId, '|', rows[0].currentTitle, '->', rows[0].proposedTitle); }
+ console.log(' reuse datasource:', ds.name || ds.error, ds.linked ? '(linked to primary ✓)' : '');
+ if (ds.duplicates?.length) console.log(' EMPTY DUPLICATE title datasources to delete:', ds.duplicates.join(', '));
console.log(' wrote:', out);
- console.log('\n Fire (Steve only): node push-mdc-sample-titles.mjs --apply --i-am-steve <dataSourceName>');
+ console.log('\n Fire (Steve only): node push-mdc-sample-titles.mjs --apply --i-am-steve');
})();
}
diff --git a/push-mdc-sample-titles.mjs b/push-mdc-sample-titles.mjs
index 394de81..478d44b 100644
--- a/push-mdc-sample-titles.mjs
+++ b/push-mdc-sample-titles.mjs
@@ -1,20 +1,20 @@
#!/usr/bin/env node
/**
* push-mdc-sample-titles.mjs — GATED WRITE. Push the MDC "Sample —" Google-title overrides
- * to a GMC supplemental data source (Merchant API v1), keyed by offerId. (TK-11307 verdict B)
+ * to the EXISTING, primary-linked "DW Sample Title Overrides" supplemental data source,
+ * keyed by offerId. (TK-11307 verdict B)
*
* ⚠️ STEVE-GATED, customer-facing (Google listing). DEFAULT = DRY-RUN (no writes).
- * A real run requires BOTH: --apply AND --i-am-steve AND a <dataSourceName>.
- * It overrides ONLY the GOOGLE title via the supplemental source — it NEVER touches
- * Shopify, price, availability, or the on-site PDP. Fully reversible: delete the
- * supplemental data source (removes every override at once) — see the memo.
+ * A real run requires BOTH: --apply AND --i-am-steve.
+ * Overrides ONLY the GOOGLE title via the supplemental source — NEVER Shopify, price,
+ * availability, or the on-site PDP. Idempotent: re-inserting the same offerId just
+ * re-writes the same override (Google merges by offerId). Reversible: delete the
+ * productInputs (or drop the supplemental source's overrides) — see the memo.
*
- * Reads: data/mdc-sample-title-overrides.json (from build-mdc-sample-titles.mjs).
- * GUARD: HARD-ABORTS unless EVERY row's proposedTitle begins with "Sample" (the whole point).
- *
- * Create the data source first (dedicated, so rollback = delete just this source):
- * node step-a-create-datasource.js # (rename displayName to 'DW MDC Sample Title Overrides' first)
- * then: node push-mdc-sample-titles.mjs --apply --i-am-steve accounts/146735262/dataSources/<id>
+ * IDEMPOTENCY: reuses the SINGLE existing linked datasource resolved by displayName — it does
+ * NOT create a datasource, so a re-fire can never spawn duplicates.
+ * GUARD: HARD-ABORTS unless EVERY row's proposedTitle begins with "Sample".
+ * Optional: pass an explicit `accounts/146735262/dataSources/<id>` to override the resolver.
*/
import fs from 'node:fs';
import path from 'node:path';
@@ -22,42 +22,42 @@ import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { token, MERCHANT } = require('./_auth');
+const { resolveSampleTitleDS } = await import('./build-mdc-sample-titles.mjs');
const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const args = Object.fromEntries(process.argv.slice(2).map(a => { const [k, v] = a.replace(/^--/, '').split('='); return [k, v === undefined ? true : v]; }));
-const DS = process.argv.slice(2).find(a => a.startsWith('accounts/'));
+const argv = process.argv.slice(2);
+const args = Object.fromEntries(argv.map(a => { const [k, v] = a.replace(/^--/, '').split('='); return [k, v === undefined ? true : v]; }));
+const DS_ARG = argv.find(a => a.startsWith('accounts/'));
const APPLY = args.apply === true && args['i-am-steve'] === true;
const LIST_PATH = path.join(__dirname, 'data', 'mdc-sample-title-overrides.json');
const list = JSON.parse(fs.readFileSync(LIST_PATH, 'utf8'));
-// HARD GUARD — every proposed Google title MUST begin with "Sample".
const bad = list.filter(r => !/^Sample\b/.test(String(r.proposedTitle || '')));
if (bad.length) {
console.error(`⛔ ABORT: ${bad.length}/${list.length} proposed titles do NOT begin with "Sample". Rebuild the list.`);
- console.error(` e.g. ${bad.slice(0, 3).map(r => r.offerId + ': ' + r.proposedTitle).join(' | ')}`);
process.exit(1);
}
-console.log(`push-mdc-sample-titles — mode: ${APPLY ? '⚠️ LIVE APPLY' : 'DRY-RUN (no writes)'}`);
-console.log(`overrides: ${list.length} (all begin with "Sample" ✓)`);
-if (!APPLY || !DS) {
- console.log('\nDRY-RUN or no data source. To execute (Steve only):');
- console.log(' 1) node step-a-create-datasource.js # create the supplemental source, copy its name');
- console.log(' 2) node push-mdc-sample-titles.mjs --apply --i-am-steve accounts/146735262/dataSources/<id>');
- if (list[0]) console.log(`\n first override: ${list[0].offerId}\n ${list[0].currentTitle}\n -> ${list[0].proposedTitle}`);
- process.exit(0);
-}
-
const sleep = ms => new Promise(r => setTimeout(r, ms));
(async () => {
- let tok = await token(), tokAt = Date.now(), ok = 0, fail = 0;
+ const tok = await token();
+ let DS = DS_ARG;
+ if (!DS) { const r = await resolveSampleTitleDS(tok); DS = r.name; console.log(`resolved datasource: ${DS} ${r.linked ? '(linked to primary ✓)' : '(NOT LINKED — override will not merge!)'}`); }
+ console.log(`push-mdc-sample-titles — mode: ${APPLY ? '⚠️ LIVE APPLY' : 'DRY-RUN (no writes)'}`);
+ console.log(`overrides: ${list.length} (all begin with "Sample" ✓) → ${DS}`);
+ if (!APPLY) {
+ console.log('\nDRY-RUN. To execute (Steve only): node push-mdc-sample-titles.mjs --apply --i-am-steve');
+ if (list[0]) console.log(` first: ${list[0].offerId}\n ${list[0].currentTitle}\n -> ${list[0].proposedTitle}`);
+ process.exit(0);
+ }
+ let tokAt = Date.now(), tk = tok, ok = 0, fail = 0;
for (let i = 0; i < list.length; i++) {
- if (Date.now() - tokAt > 50 * 60 * 1000) { tok = await token(); tokAt = Date.now(); }
+ if (Date.now() - tokAt > 50 * 60 * 1000) { tk = await token(); tokAt = Date.now(); }
const row = list[i];
const url = `https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/productInputs:insert?dataSource=${encodeURIComponent(DS)}`;
const body = { offerId: row.offerId, contentLanguage: 'en', feedLabel: 'US', productAttributes: { title: row.proposedTitle } };
- const r = await fetch(url, { method: 'POST', headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+ const r = await fetch(url, { method: 'POST', headers: { Authorization: 'Bearer ' + tk, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (r.ok) ok++; else { fail++; if (fail <= 10) console.error('FAIL', row.offerId, r.status, (await r.text()).slice(0, 120)); }
if (i % 500 === 0) console.log(` ${i}/${list.length} | ok ${ok} fail ${fail}`);
if (r.status === 429) await sleep(3000);
diff --git a/test-mdc-sample-titles.mjs b/test-mdc-sample-titles.mjs
index 21a1520..6581449 100644
--- a/test-mdc-sample-titles.mjs
+++ b/test-mdc-sample-titles.mjs
@@ -16,7 +16,16 @@ const MDC = { title: 'London All About Big Geo Platinum White Wallcovering | Ph
const SELL = { title: 'Spazzolato Bronze Wallcovering | Phillipe Romano', tags: ['Type II', 'Vinyl'], variants: [{ legacyResourceId: '1', sku: 'DWPX-500001-Yard' }, { legacyResourceId: '2', sku: 'DWPX-500001-Sample' }] };
const PJ = { title: 'Untethered Wallcovering by Phillip Jeffries', tags: ['Grasscloth', 'Showroom'], variants: [{ legacyResourceId: '9', sku: 'PJ-Sample' }] };
+// Offer identity the builder constructs: offerId = BARE sample-variant legacyResourceId,
+// GMC product name = "accounts/<merchant>/products/en~US~<offerId>" (legacy bare-id feed).
+const MERCHANT = '146735262';
+const mdcVar = sampleVariant(MDC.variants);
+const mdcOfferId = String(mdcVar.legacyResourceId);
+const mdcGmcName = `accounts/${MERCHANT}/products/en~US~${mdcOfferId}`;
+
const cases = [
+ ['offerId is the BARE variant id (not compound shopify_US_)', mdcOfferId, '44594816745523'],
+ ['GMC name uses en~US~ (no online~ prefix, no shopify_US_ compound)', mdcGmcName, 'accounts/146735262/products/en~US~44594816745523'],
['MDC Google title BEGINS with "Sample"', mdcSampleTitle(MDC.title).startsWith('Sample'), true],
['MDC Google title drops the "| Phillipe Romano" suffix', /Phillipe Romano/.test(mdcSampleTitle(MDC.title)), false],
['MDC Google title is a memo swatch', /Memo Swatch$/.test(mdcSampleTitle(MDC.title)), true],
← 37f1e17 gmc: MDC Google sample-title override (DTD verdict B, TK-113
·
back to Gmc Titlefix
·
auto-data-snapshot: 2026-09-09T08:26:36 (1 data files) — dat 6b3b6dd →