You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
data: read-only inventory for the Item migration (seed key drift, legacy keys, slug collisions) #1148
M0. Run the inventory queries from the design doc read-only (public anon REST or approved read-only access only, no writes, no secrets) and write the data-fix list: seed key drift, legacy keys, slug collisions. Anything needing a privileged read goes to the PO as a request for JOY.
Part of the Item / variant / listing model approved by JOY on 2026-10-27 (#1129, design doc Docs/Proposals/2026-10-27-item-variant-listing-model.md, section 10 task 2).
Depends on: none
See the design doc sections 4, 5 and 7 for the schema, impact and migration plan.
Claimed by a Core sub-session of the Rate Core PO. Running the M0 inventory (design doc section 7.3) read-only through the public anon REST endpoint and the public site API. No writes, no secrets. Anything needing a privileged read will be listed as an exact SQL request for JOY.
[Agent: Core] M0 inventory and data-fix list for the Item migration (#1129 design doc, sections 7 and 7.3).
How it was read
Prod project gulptwduchsjcsbndmua, schema rate, through the public anon REST endpoint that rate.vn uses (the publishable key in the site bundle). SELECTs only, about 15 requests in total, run on 2026-10-28.
Anon can read every row of master_products and products: both have a FOR SELECT USING (true) policy in the reference copy. price_alerts is not readable by anon (42501).
Seed and normalizer checks ran today's ProductNormalizer (dev 645c155e) on every seed name, master name and listing title. SQL cannot do this part.
Masters total / published / draft / archived / NULL status
37 / 16 / 21 / 0 / 0
bad_shape (not three segments)
0
khc: keys
0
:unknown masters
0
:standard masters
2
Items (distinct brand:model)
37
Items with more than one variant
0
Items whose variants disagree on brand or category
0
Listings total / without a master / on a missing master
128 / 0 / 0
Item slug collisions (section 7.1 helper)
0
Item names still carrying a size after the 7.1 helper
0
Earbuds with a size in the key
5
Masters whose key differs from the normalizer key
6
Data-fix list
1. Seed key drift: 6 masters (fix in M0, before M2)
The normalizer keys these differently from the stored key, so Tier 1 never hits them. No master holds the normalizer key yet, so no duplicate exists today. A re-key in place is enough: no archiving, no merge, no alert move. Ids and slugs stay the same, so the FKs from listings and alerts and the public URLs do not change.
Stored model_key
Normalizer key
Status
Listings
Slug (unchanged)
samsung:galaxy-buds3-pro:64gb
samsung:galaxy-buds3-pro:unknown
published
4
samsung-galaxy-buds3-pro
apple:airpods-pro-3:standard
apple:airpods-pro-3:unknown
published
9
apple-airpods-pro-3-usb-c
sony:wf-1000xm5:64gb
sony:wf-1000xm5:unknown
draft
8
sony-wf-1000xm5
sony:wf-1000xm6:standard
sony:wf-1000xm6:unknown
draft
9
sony-wf-1000xm6
apple:airpods-pro-2-usbc:64gb
apple:airpods-pro-2:unknown
draft
4
apple-airpods-pro-2-usb-c
apple:watch-ultra-3-49mm:64gb
apple:watch-ultra-3:64gb
draft
4
apple-watch-ultra-3-49mm-64gb
38 listings sit on these 6 masters. Every listing title keys to the normalizer key, not the stored key. Sample: tgdd-tai-nghe-bluetooth-true-wireless-samsung-galaxy-buds-3-pro-r630n ("Tai nghe TWS Samsung Galaxy Buds3 Pro R630N") keys as samsung:galaxy-buds3-pro:unknown; hhm-MEWM4SA ("Apple Watch Ultra 3 - GPS + Cellular, 49mm ...") keys as apple:watch-ultra-3:64gb.
UPDATErate.master_products mp SET storage_gb =NULL, updated_at = now()
FROMrate.categories c
WHEREc.id=mp.category_idANDc.slug='earbuds'ANDmp.storage_gbIS NOT NULL;
apple:airpods-pro-2-usbc becomes Item apple:airpods-pro-2. The normalizer does not split USB-C from Lightning. That is fine today (only the USB-C model is sold new), but if a Lightning listing shows up it will join the same Item.
After the fix, the 5 earbuds Items are single-variant Items with an unknown variant, as section 4.2 intends. The M3 guard query ("sized earbuds") then returns 0 rows.
2. Legacy keys: none
No khc: keys, no key outside the three-segment shape, no NULL status. Section 7.1's legacy branch has nothing to handle. No action.
3. Slug collisions: none
With the 7.1 helper (strip -(\d+(gb|tb)|standard|unknown)$), the 37 masters give 37 distinct Item slugs. No Item slug equals another Item's variant slug. No action.
Two notes for the M2 and task 5 authors, neither of them a collision:
The 5 earbuds slugs have no size suffix, so their Item slug equals their variant slug. Section 7.3 allows this ("An Item slug may only collide with one of its own variants' slugs").
11 stored slugs (3 of them on the drift rows above) differ from the slug the normalizer would build today, for example apple-ipad-pro-m5-11-inch-256gb vs apple-ipad-pro-m5-11-256gb, apple-watch-series-11-46mm-gps-64gb vs apple-watch-series-11-46mm-64gb, samsung-galaxy-tab-s10-ultra-5g-256gb vs samsung-galaxy-tab-s10-ultra-256gb. Matching is by key, so nothing breaks. But a new size of an existing Item must take the Item's stored slug, not a freshly built one, or the Item and its new variant get unrelated slugs (task 5).
4. Multi-size Items and unknown masters: none yet
37 Items, each with one master. There is no :unknown master, so M3 has nothing to retire today, and the 7.3 query "unknown masters with alerts or reviews" returns 0 rows whatever the alerts table holds. No privileged read is needed for it.
5. A storage-less listing on a sized master (feeds #1105 / task 5)
fptshop fpt-00911517 ("Apple Watch Series 10 GPS 42mm Viền nhôm Dây vải") sits on apple:watch-series-10-42mm:64gb (draft), but its title keys as apple:watch-series-10-42mm:unknown. The registry has no apple:watch-series-10 entry with only_storage_gb: 64, unlike watch-series-11, watch-ultra-2 and watch-ultra-3. Under the new model it would land on the Item with no variant.
Proposed fix: add an apple:watch-series-10 registry entry with only_storage_gb: 64 in task 6 (seeds per Item), or as a small normalizer fix before task 5. Code only, no data change.
Their price_max feeds the master's max_price. This is the known defect of section 4.4. Proposed fix: none in M0. Task 5 (SKU's own price as price_max) or M5 (listing_skus) handles it.
7. Draft masters with no listing: 6 (information only)
apple:iphone-16-pro:256gb, lenovo:thinkpad-x1-carbon-gen-12:512gb, samsung:galaxy-a55:256gb, xiaomi:17-pro-max:512gb, xiaomi:18-pro-max:512gb, xiaomi:pad-6s-pro:256gb. The backfill gives each a draft Item. No action.
Privileged reads
None of the M0 decisions above needs one. One optional check for JOY, to confirm that anon sees every row (the DOS-Me ledger has 8 rate migrations not mirrored here, and one could have narrowed a policy):
SELECT (SELECTcount(*) FROMrate.master_products) AS masters, -- anon saw 37
(SELECTcount(*) FROMrate.products) AS listings, -- anon saw 128
(SELECTcount(*) FROMrate.productsWHERE cluster_id IS NULL) AS no_master, -- anon saw 0
(SELECTcount(*) FROMrate.price_alerts a JOINrate.master_products mp ONmp.id=a.master_product_idWHEREmp.model_keyIN ('samsung:galaxy-buds3-pro:64gb','apple:airpods-pro-3:standard',
'sony:wf-1000xm5:64gb','sony:wf-1000xm6:standard','apple:airpods-pro-2-usbc:64gb',
'apple:watch-ultra-3-49mm:64gb')) AS alerts_on_rekeyed; -- information only: the re-key keeps ids
The backfill can run as drafted. It meets no legacy keys, no slug collision, no multi-size Item and no unlinked listing.
Order: data: migrate stored model_keys for earbuds and Watch Ultra 3 to normalizer keys #1123's re-key and seed change first, so the Items are created with the normalizer's Item keys (apple:watch-ultra-3, apple:airpods-pro-2). If M2 runs first, these two Items get the old keys, and fixing them later means re-keying an Item as well as a master.
Activity
JOY commentedon Oct 27, 2026
Claimed by a Core sub-session of the Rate Core PO. Running the M0 inventory (design doc section 7.3) read-only through the public anon REST endpoint and the public site API. No writes, no secrets. Anything needing a privileged read will be listed as an exact SQL request for JOY.
JOY commentedon Oct 27, 2026
[Agent: Core] M0 inventory and data-fix list for the Item migration (#1129 design doc, sections 7 and 7.3).
How it was read
gulptwduchsjcsbndmua, schemarate, through the public anon REST endpoint that rate.vn uses (the publishable key in the site bundle). SELECTs only, about 15 requests in total, run on 2026-10-28.master_productsandproducts: both have aFOR SELECT USING (true)policy in the reference copy.price_alertsis not readable by anon (42501).ProductNormalizer(dev645c155e) on every seed name, master name and listing title. SQL cannot do this part.apps/importer/src/shopee/jobs/item-inventory-report.ts(PR feat(importer): read-only Item migration inventory report (#1148) #1168). It is read-only and uses the anon key.Counts (section 7.3 inventory)
bad_shape(not three segments)khc:keys:unknownmasters:standardmastersbrand:model)Data-fix list
1. Seed key drift: 6 masters (fix in M0, before M2)
The normalizer keys these differently from the stored key, so Tier 1 never hits them. No master holds the normalizer key yet, so no duplicate exists today. A re-key in place is enough: no archiving, no merge, no alert move. Ids and slugs stay the same, so the FKs from listings and alerts and the public URLs do not change.
model_keysamsung:galaxy-buds3-pro:64gbsamsung:galaxy-buds3-pro:unknownsamsung-galaxy-buds3-proapple:airpods-pro-3:standardapple:airpods-pro-3:unknownapple-airpods-pro-3-usb-csony:wf-1000xm5:64gbsony:wf-1000xm5:unknownsony-wf-1000xm5sony:wf-1000xm6:standardsony:wf-1000xm6:unknownsony-wf-1000xm6apple:airpods-pro-2-usbc:64gbapple:airpods-pro-2:unknownapple-airpods-pro-2-usb-capple:watch-ultra-3-49mm:64gbapple:watch-ultra-3:64gbapple-watch-ultra-3-49mm-64gb38 listings sit on these 6 masters. Every listing title keys to the normalizer key, not the stored key. Sample:
tgdd-tai-nghe-bluetooth-true-wireless-samsung-galaxy-buds-3-pro-r630n("Tai nghe TWS Samsung Galaxy Buds3 Pro R630N") keys assamsung:galaxy-buds3-pro:unknown;hhm-MEWM4SA("Apple Watch Ultra 3 - GPS + Cellular, 49mm ...") keys asapple:watch-ultra-3:64gb.Proposed fix
sony:wf-1000xm5,sony:wf-1000xm6,apple:airpods-pro-2-usbc) are in the same SQL and need the same seed change.storage_gb = NULLon the five earbuds rows. Today they hold 64, 64, 64, 1 and 1. The normalizer gives earbuds a NULL storage (ProductNormalizer: earbuds keyed :1gb, iPad RAM taken as storage, AirPods Pro by year #643, ProductNormalizer: benchmark and phone-template specs override the title (RAM, earbud storage, SAR, Face ID) #657), and the1on the two:standardrows matches no key:flagship-seed-data.tschange (six seed keys to the normalizer keys, andstorage_gb: nullon the earbuds) merges right after the migration, as data: migrate stored model_keys for earbuds and Watch Ultra 3 to normalizer keys #1123 requires.backfill-specs.ts --applymust not run between the two steps, or it inserts duplicate masters.apple:airpods-pro-2-usbcbecomes Itemapple:airpods-pro-2. The normalizer does not split USB-C from Lightning. That is fine today (only the USB-C model is sold new), but if a Lightning listing shows up it will join the same Item.unknownvariant, as section 4.2 intends. The M3 guard query ("sized earbuds") then returns 0 rows.2. Legacy keys: none
No
khc:keys, no key outside the three-segment shape, no NULL status. Section 7.1's legacy branch has nothing to handle. No action.3. Slug collisions: none
With the 7.1 helper (strip
-(\d+(gb|tb)|standard|unknown)$), the 37 masters give 37 distinct Item slugs. No Item slug equals another Item's variant slug. No action.Two notes for the M2 and task 5 authors, neither of them a collision:
apple-ipad-pro-m5-11-inch-256gbvsapple-ipad-pro-m5-11-256gb,apple-watch-series-11-46mm-gps-64gbvsapple-watch-series-11-46mm-64gb,samsung-galaxy-tab-s10-ultra-5g-256gbvssamsung-galaxy-tab-s10-ultra-256gb. Matching is by key, so nothing breaks. But a new size of an existing Item must take the Item's stored slug, not a freshly built one, or the Item and its new variant get unrelated slugs (task 5).4. Multi-size Items and
unknownmasters: none yet37 Items, each with one master. There is no
:unknownmaster, so M3 has nothing to retire today, and the 7.3 query "unknown masters with alerts or reviews" returns 0 rows whatever the alerts table holds. No privileged read is needed for it.5. A storage-less listing on a sized master (feeds #1105 / task 5)
fptshop fpt-00911517("Apple Watch Series 10 GPS 42mm Viền nhôm Dây vải") sits onapple:watch-series-10-42mm:64gb(draft), but its title keys asapple:watch-series-10-42mm:unknown. The registry has noapple:watch-series-10entry withonly_storage_gb: 64, unlikewatch-series-11,watch-ultra-2andwatch-ultra-3. Under the new model it would land on the Item with no variant.Proposed fix: add an
apple:watch-series-10registry entry withonly_storage_gb: 64in task 6 (seeds per Item), or as a small normalizer fix before task 5. Code only, no data change.6. Listings whose price spans several SKUs: 3 (design 4.4 defect)
price_minprice_maxapple:iphone-15:128gbapple:iphone-16:128gbsony:wf-1000xm6:standard50757465973Their
price_maxfeeds the master'smax_price. This is the known defect of section 4.4. Proposed fix: none in M0. Task 5 (SKU's own price asprice_max) or M5 (listing_skus) handles it.7. Draft masters with no listing: 6 (information only)
apple:iphone-16-pro:256gb,lenovo:thinkpad-x1-carbon-gen-12:512gb,samsung:galaxy-a55:256gb,xiaomi:17-pro-max:512gb,xiaomi:18-pro-max:512gb,xiaomi:pad-6s-pro:256gb. The backfill gives each a draft Item. No action.Privileged reads
None of the M0 decisions above needs one. One optional check for JOY, to confirm that anon sees every row (the DOS-Me ledger has 8
ratemigrations not mirrored here, and one could have narrowed a policy):Effect on M2 (#1150)
apple:watch-ultra-3,apple:airpods-pro-2). If M2 runs first, these two Items get the old keys, and fixing them later means re-keying an Item as well as a master.feat(importer): read-only Item migration inventory report (#1148) (#1168
JOY commentedon Oct 28, 2026
[Agent: PO] Done: merged into dev (PR #1168). Docs/tooling change, no production runtime effect, so Done at merge per the lifecycle rule.