Files
inkreach-official-website/plans/fix/sds-sync-guard-fix.md
T
yeuimu 6c61a4e871 feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma
  binaryTargets for openssl 3.0
- nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal
  cron, http->https redirect
- prisma: add origin_goods.delisted migration, sync missing schema
  (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz
- api: CORS wildcard reflection, helmet CORP cross-origin, price
  backfill in persistProductDetail, categoryIcon ancestor fallback,
  mediaByColor per-color gallery in public goods detail
- admin: /admin base path (vite + router)
- import-data.mjs: udt_name casting, serial sequence advance fix
2026-08-26 14:23:09 +08:00

4.6 KiB

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/2delisted=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.