# SDS Sync Guard Fix Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Prevent the SDS (mapi.sdspod.com) sync from deleting the category/product library when the upstream API returns a degenerate/partial response, and recover the data lost in the 2026-08-20 incident. **Background / Root cause:** - On 2026-08-20 06:31 a manual sync ran while `category/tree/3` returned only 2 nodes (normal: 226). - `syncCategories` treats SDS as the single source of truth: `staleDeleted=225` deleted all previously synced SDS categories. - With SDS categories gone, `syncProducts` had `leafCategories=0/2` → `delisted=523` marked 523/536 origin goods as delisted. - Result: all 536 origin goods `delisted=true`; all 240 website `Good` rows reference delisted origin goods → `/public/goods` returns `total: 0` → product-center page appears empty. **Architecture:** Add safety guards to `SyncService` and `SdsClientService` so a partial/failed upstream response never triggers destructive operations (`staleDeleted` / delist marking). Recovery is a data fix (re-enable delisted origin goods) followed by normal re-sync. **Tech Stack:** NestJS, TypeScript, Prisma, Jest. --- ### Task 1: Data recovery — re-enable all origin goods **Files:** - Run ad-hoc prisma script (read+write to DB via apps/api @prisma/client) - [ ] **Step 1: Reset `delisted=false` for all `originGood` rows** All 536 rows were mis-flagged by the 08-20 partial sync. Run `prisma.originGood.updateMany({ where: {}, data: { delisted: false } })`. - [ ] **Step 2: Trigger category sync then product sync** Verify `/sync/status` logs show SUCCESS with healthy numbers (categories ≈ 225+, products ≈ 519+). - [ ] **Step 3: Verify `/public/goods` non-empty and product-center page renders products.** ### Task 2: Hardening — category sync stale-deletion guard **Files:** - Modify: `apps/api/src/sync/sync.service.ts` - Modify: `apps/api/src/sync/sync.service.spec.ts` - [ ] **Step 1: Write failing test** Given existing SDS-linked categories in DB, when `fetchCategoryTree` returns a degenerate small tree (< MIN_CATEGORY_COUNT), `syncCategories` must NOT delete existing categories (no stale deletion) and must log a warning. - [ ] **Step 2: Run test, verify RED.** - [ ] **Step 3: Implement guard** Before the stale-deletion block, compute `existingSdsCount = categories with sdsCategoryId`. Skip stale deletion when `flat.length < MIN_CATEGORY_COUNT (10)` OR `flat.length < MIN_CATEGORY_RATIO (0.5) * existingSdsCount`. Log warning with counts. - [ ] **Step 4: Verify GREEN + no regressions.** ### Task 3: Hardening — product sync delist guard **Files:** - Modify: `apps/api/src/sync/sync.service.ts` - Modify: `apps/api/src/sync/sync.service.spec.ts` - [ ] **Step 1: Write failing test** When there are very few leaf categories (< MIN_LEAF_CATEGORIES) or very few seen products (< MIN_SEEN_GOODS), `syncProducts` must skip the delist/reactivate marking and log a warning instead of mass-delisting. - [ ] **Step 2: Run test, verify RED.** - [ ] **Step 3: Implement guard** Only run delist detection when `leafRows.length >= MIN_LEAF_CATEGORIES (10)` AND `seenSdsGoodIds.size >= MIN_SEEN_GOODS (50)`. Otherwise skip and log warning. - [ ] **Step 4: Verify GREEN + no regressions.** ### Task 4: Hardening — SDS client response sanity validation **Files:** - Modify: `apps/api/src/sync/sds-client.service.ts` - Modify: `apps/api/src/sync/sds-client.service.spec.ts` (create if absent) - [ ] **Step 1: Write failing test** `fetchCategoryTree` throws when the returned array is degenerate (e.g. fewer than `MIN_SDS_CATEGORY_NODES`), so callers never operate on a bad tree. - [ ] **Step 2: Run test, verify RED.** - [ ] **Step 3: Implement** If the fetched category array length < MIN_SDS_CATEGORY_NODES (10), throw an Error with the received count so sync logs FAILED (non-destructive) instead of deleting data. - [ ] **Step 4: Verify GREEN + no regressions.** ### Task 5: Full verification - [ ] **Step 1:** Run `apps/api` full jest suite — all green. - [ ] **Step 2:** Typecheck `apps/api` (`tsc -p tsconfig.json --noEmit` or nest build). - [ ] **Step 3:** Trigger a real sync via the admin UI / HTTP; confirm sync logs show healthy category/product counts and `/public/goods` returns data. - [ ] **Step 4:** Confirm product-center page at `http://192.168.124.137:3000/product-center` renders products.