diff --git a/Docs/Proposals/2026-10-28-extension-data-source.html b/Docs/Proposals/2026-10-28-extension-data-source.html
new file mode 100644
index 00000000..11f6125a
--- /dev/null
+++ b/Docs/Proposals/2026-10-28-extension-data-source.html
@@ -0,0 +1,306 @@
+
+
+
+
+
Đề xuất, chưa code
+
Chờ JOY trả lời 7 câu
+
Mở rộng extension thành nguồn dữ liệu chính
+
Ngày 28/10/2026 - Issue DOS/Rate#1175 - Bản tóm tắt tiếng Anh: 2026-10-28-extension-data-source.md - Người soạn: session con của Rate Core PO
+
+
+
Tóm tắt một đoạn
+
Crawler đang đụng tường bot (TikTok tạm dừng ở RS#658, Lazada search bị trả về trang chặn (punish page) ở RS#659, Shopee chỉ chạy được qua một runner đã đăng nhập). Trình duyệt thật của người dùng không gặp những tường này vì chính người dùng mở trang. Đề xuất: phương án B, cho extension gửi một quan sát tối thiểu (giá, tồn kho, mã SKU, URL, thời điểm, sàn, không có dữ liệu cá nhân) khi người dùng đã đồng ý, lưu vào vùng cách ly, và chỉ đưa lên giá công khai sau khi Rate Services xác minh chéo với crawler hoặc với người dùng độc lập khác. Crawler vẫn giữ làm nguồn khởi động và đối chứng. Extension vẫn công khai (người dùng tải về thì giải nén ra đọc được), còn luật chống đầu độc và chấm điểm tin cậy nằm ở DOS/Rate-Services private. Extension không bao giờ tự tải trang người dùng không mở, không né bot, không giải captcha.
+
+
+
1. Hiện trạng apps/extension (đọc từ mã, nhánh dev ngày 28/10)
+
+
1.1. Extension đọc gì
+
+ - Chạy content script trên 10 sàn và chuỗi bán lẻ: Shopee, Lazada, Tiki, TikTok (
*.tiktok.com), CellphoneS, Thế Giới Di Động, FPT Shop, Hoàng Hà, Di Động Việt, Phong Vũ (manifest.json). Quyền khai báo: storage, sidePanel và host của 10 sàn cộng rate.vn.
+ - Chỉ xử lý trang chi tiết sản phẩm có mã sản phẩm trong URL, bỏ trang chủ, danh mục, tìm kiếm (
extractProductSignalFromDom).
+ - Lấy: URL đã cắt tham số theo dõi, mã sản phẩm và mã shop từ URL, tiêu đề, phiên bản đang chọn, giá hiện tại, giá gạch ngang, số sao và số lượt đánh giá. Ưu tiên JSON-LD, sau đó mới tới selector DOM. Chưa đọc tồn kho.
+
+
+
1.2. Hai luồng gửi lên server
+
Mỗi lần mở trang sản phẩm (luôn chạy, không cần opt-in):
+ GET /api/v1/rate/extension/lookup?platform&url&title&price&...
+ -> chỉ đọc, trả giá lịch sử, nơi bán khác, phán định giá ảo. Không ghi DB.
+
+Chỉ khi người dùng bật "Đóng góp phát hiện sản phẩm mới (Opt-In)" (mặc định TẮT):
+ POST /api/v1/rate/extension/ingest { platform, externalItemId, externalShopId,
+ url, title, selectedVariant, price, originalPrice, ratingStar,
+ ratingCount, userConsentOptIn: true }
+ -> Zod + giới hạn 15 lần/phút/IP (bộ nhớ trong của từng instance serverless,
+ không phải giới hạn toàn cục) + chặn phụ kiện + kiểm host khớp sàn
+ -> RPC rate.enqueue_crowd_verify_signal (không dùng service role)
+ -> chèn 1 job 'extension_crowd_verify' vào rate.crawl_jobs
+ (trần 120 job/phút toàn hệ thống, đây là giới hạn toàn cục duy nhất)
+ -> worker Rate Services nhận job như một yêu cầu CÀO LẠI URL đó
+
+
1.3. "Cách ly" hiện tại nghĩa là gì
+
+ - Tín hiệu từ người dùng không bao giờ ghi thẳng vào giá công khai. Giá người dùng thấy chỉ nằm trong
payload của job.
+ - Theo đọc mã Rate Services (mô tả chung), worker không có nhánh xử lý riêng cho loại job này: nó cào lại URL như mọi job khác. Tức là giá người dùng thấy không được dùng làm dữ liệu, chỉ là cái cớ để cào. Khi crawler bị chặn ở sàn đó, tín hiệu cộng đồng hiện không giúp được gì. Đây là lý do chính cần đề xuất này.
+
+
+
+
1.4. Những điểm lệch phát hiện khi đọc mã (cần sửa dù JOY chọn phương án nào)
+
+ - Nhãn opt-in trong side panel nói "Chỉ gửi URL sản phẩm công khai đã cắt sạch tham số theo dõi", nhưng payload thật gửi mười trường dữ liệu (cộng cờ đồng ý): nền tảng, mã sản phẩm, mã shop, URL, tiêu đề, phiên bản, giá, giá gốc, số sao, số lượt đánh giá. Lời công bố phải khớp dữ liệu gửi đi; Chrome Web Store coi công bố nổi bật trong giao diện là điều kiện để sự đồng ý có giá trị [6].
+ CHROMEWEBSTORE.md giải trình quyền tabs và host localhost:3000 nhưng manifest.json không có hai quyền này; ngược lại manifest có năm host mà tài liệu không giải trình: TikTok, Hoàng Hà, Di Động Việt, Phong Vũ, www.rate.vn. Mục công bố quyền riêng tư của tài liệu này cũng chỉ kể 6 tên miền và nói extension "chỉ chạy trên trang chi tiết sản phẩm", trong khi content script khớp mọi đường dẫn của 10 sàn (việc lọc trang sản phẩm diễn ra trong mã, sau khi script đã chạy).
+ - Host
https://*.tiktok.com/* cho content script chạy trên toàn bộ TikTok (cả video, tin nhắn), rộng hơn mức cần cho trang sản phẩm. Match pattern hỗ trợ giới hạn theo đường dẫn, nên có thể thu hẹp.
+ - Trang
/privacy-policy của rate.vn chưa nhắc tới extension.
+ - Luồng lookup mặc định, không cần opt-in, đã gửi URL, tiêu đề, giá, mã sản phẩm, mã shop (và IP theo bản chất HTTP) lên rate.vn ở mọi lần mở trang sản phẩm. Chính sách Chrome cho phép gửi hoạt động duyệt web khi phục vụ tính năng người dùng thấy và được công bố nổi bật [6]; giả định của người soạn là luồng này hợp lệ nếu được công bố rõ, nhưng nó cũng là dữ liệu duyệt web theo Luật 91/2025/QH15, nên cần đưa vào câu hỏi cho ý kiến pháp lý (mục 4.7).
+
+
Chưa xác minh extension đã được phát hành trên Chrome Web Store hay chưa.
+
+
+
2. Honey, Keepa, BeeCost lấy dữ liệu thế nào (chỉ từ nguồn công khai)
+
+ | Sản phẩm | Điều đã công bố hoặc đã được kiểm chứng | Bài học cho Rate |
+ | Honey (PayPal) |
+ Trang "Data and privacy" của Honey nói họ thu thập thông tin sản phẩm chung như giá và tình trạng còn hàng để chạy lịch sử giá và Droplist, chỉ trên các trang mua sắm họ hỗ trợ, và dữ liệu sự kiện không chứa thông tin định danh [1]. Tổ chức datarequests.org cho rằng Honey thu thập lịch sử duyệt web nhiều hơn mức chính sách nói và đã khiếu nại lên cơ quan bảo vệ dữ liệu [2]. |
+ Mô hình gần nhất với đề xuất: extension đọc giá trên trang người dùng xem. Rủi ro uy tín lớn nhất là làm nhiều hơn điều mình công bố. |
+ | Keepa |
+ Trang Firefox Add-ons của Keepa nói extension gửi mã ASIN của sản phẩm đang xem để vẽ biểu đồ và không lưu gì về người dùng [3]. Nhà nghiên cứu Wladimir Palant (2021) ghi nhận extension giữ kết nối WebSocket với một mã người dùng cố định, gửi số sao và hạng bán, và nhận lệnh từ server để tải nền các trang Amazon người dùng không mở rồi gửi dữ liệu về [4]. Một thảo luận trên nhóm Chromium Extensions cũng nêu việc này [5]. Keepa còn cung cấp dữ liệu lịch sử giá qua API cho bên ngoài [5b]. |
+ Ví dụ cần tránh: biến máy người dùng thành crawler phân tán. Đó đúng là logic cào dữ liệu, trái tinh thần chính sách store, và không được đặt trong DOS/Rate. Rate chỉ đọc trang người dùng tự mở. |
+ | BeeCost (Việt Nam) |
+ Có extension cho Chrome và Cốc Cốc, hiện lịch sử giá trên Shopee, Tiki, Lazada, Sendo [7][8]; nhà phát triển nói có hơn 100 triệu sản phẩm để so sánh [9]. Một bài đánh giá nói khả năng lấy dữ liệu tốt nhất trên Shopee, chưa ổn định trên Lazada và FPT Shop [10]. Không tìm thấy tài liệu công khai nào mô tả cách BeeCost thu thập dữ liệu hay chính sách quyền riêng tư của họ. |
+ Chứng minh người dùng Việt Nam chấp nhận extension lịch sử giá, và Cốc Cốc là kênh phân phối đáng kể. Không suy đoán thêm về cách họ làm. |
+
+
Các nguồn [2], [4], [5] là bên thứ ba và có thể đã cũ; chúng được dẫn để thấy rủi ro, không phải để kết luận hành vi hiện tại của các sản phẩm đó.
+
+
3. Vì sao trình duyệt thật né được tường bot
+
+ - Trang đã được chính người dùng mở, có cookie, phiên đăng nhập, dấu vân tay trình duyệt và IP dân dụng thật. Extension chỉ đọc DOM đã hiện ra, không phát thêm request nào tới sàn, nên sàn không có gì khác thường để chặn.
+ - Lưu lượng tự nhiên phân tán: mỗi người xem vài trang, không có đỉnh truy cập từ một IP.
+ - Đổi lại: Rate không chọn được trang nào được xem. Dữ liệu dồn vào sản phẩm phổ biến, sản phẩm ít người xem có thể không bao giờ được cập nhật. Vì vậy crawler vẫn cần.
+
+
+
4. Những gì thay đổi nếu extension là nguồn chính
+
+
4.1. Payload quan sát tối thiểu (đề xuất ExtensionObservation v1)
+
+ | Trường | Ý nghĩa | Ghi chú |
+ schema_version | Phiên bản payload | Bắt buộc, để server từ chối bản cũ. |
+ marketplace | Sàn (10 giá trị hiện có) | Server kiểm lại từ host của URL. |
+ listing_url | URL chuẩn hóa, không query | Đã cắt tham số theo dõi như hiện nay. |
+ external_item_id, external_shop_id | Mã listing và mã shop của sàn | Mã của gian hàng, không phải của người dùng. |
+ sku_id, variant_label | Mã phiên bản (ví dụ model id của Shopee) và nhãn phiên bản đang chọn | Khớp với mô hình Item, Variant, Listing (#1129). |
+ price_vnd, list_price_vnd | Giá bán hiển thị và giá gạch ngang | Mục tiêu: chỉ lấy giá công khai chính của trang, bỏ giá sau voucher, xu, giá thành viên, vì chúng phụ thuộc tài khoản. Extractor hiện chưa phân biệt được hai loại giá này; đây là việc của M2 và cần kiểm từng sàn. |
+ stock_state, stock_qty | Còn hàng, hết hàng, sắp hết, không rõ; số lượng nếu trang hiện | Trường mới, extractor hiện chưa đọc. |
+ observed_at | Thời điểm client thấy giá | Server ghi thêm received_at và tin received_at hơn observed_at. |
+ extractor | Nguồn trích xuất (JSON-LD hay DOM) và phiên bản extension | Để loại bản extractor hỏng. |
+ submitter_id | Mã theo lượt cài, do server cấp khi người dùng đồng ý; server tự ghi thời điểm cấp | Cần cho chống đầu độc. Coi là dữ liệu cá nhân đã bút danh hóa (pseudonymous), lưu riêng, không công khai, người dùng yêu cầu xóa được. Kẻ tấn công vẫn xin được mã mới tùy ý, nên mã này chỉ giới hạn được client trung thực; phòng thủ thật nằm ở 4.4. |
+
+
Không bao giờ gửi: cookie, tên tài khoản sàn, giỏ hàng, đơn hàng, địa chỉ, số điện thoại, từ khóa tìm kiếm, referrer, query string, nội dung tab khác, lịch sử duyệt. Bỏ ratingStar, ratingCount khỏi luồng này trừ khi JOY muốn giữ.
+
IP: không lưu IP nguyên. Để kiểm "người gửi độc lập" ở 4.4, phía nhận chỉ giữ một mã băm có muối của dải mạng (ví dụ /24 hoặc ASN) trong thời gian ngắn (vài tuần), không gắn vào giá công khai. Mã băm này vẫn là dữ liệu cá nhân và phải được công bố ở 4.2.
+
+
4.2. Lời đồng ý và chính sách quyền riêng tư
+
+ - Giữ opt-in, mặc định tắt. Thêm màn hình chào khi cài lần đầu, nói rõ gửi những trường nào (kể cả mã người gửi và mã băm dải mạng), để làm gì, và nút "Không, cảm ơn" nổi bật ngang nút "Đồng ý". Theo FAQ của Chrome Web Store, sự đồng ý chỉ có giá trị khi đi kèm công bố nổi bật ngay trong giao diện extension; công bố chỉ nằm trong chính sách quyền riêng tư là không đủ [6].
+ - Có nút xem "những gì tôi đã gửi gần đây", tắt bất kỳ lúc nào, và yêu cầu xóa theo
submitter_id. Đây cũng là quyền của chủ thể dữ liệu theo Luật Bảo vệ dữ liệu cá nhân 91/2025/QH15, hiệu lực từ 01/01/2026, và Nghị định 356/2025/NĐ-CP [11][12].
+ - Cập nhật
/privacy-policy có mục riêng cho extension, kèm công bố "Limited Use": chỉ dùng cho tính năng giá, không quảng cáo, không bán, không chuyển cho bên thứ ba. FAQ yêu cầu công bố này ở trang chủ hoặc cách một cú nhấp (trang chính sách là được), và thêm một bước xác nhận riêng trong developer dashboard khi nộp [6].
+
+
+
4.3. Rủi ro duyệt Chrome Web Store và Edge Add-ons
+
+ - Mục đích duy nhất: chính sách yêu cầu dữ liệu thu thập phải cần thiết cho mục đích duy nhất đã công bố và ở mức tối thiểu [6]. Đánh giá của người soạn (không phải lời của chính sách): "kiểm tra giá và lịch sử giá" có thể bao gồm việc gửi quan sát giá, miễn là dữ liệu tối thiểu và người dùng thấy lợi ích; người duyệt store có thể nhìn khác.
+ - Hoạt động duyệt web và nội dung trang là dữ liệu người dùng theo định nghĩa của Chrome; đọc nội dung trang người dùng xem được tính là xử lý dữ liệu người dùng [6]. Vì vậy công bố và đồng ý ở 4.2 là bắt buộc, không phải tùy chọn.
+ - Rủi ro bị từ chối hoặc gỡ: lời công bố lệch payload (mục 1.4), quyền host rộng (TikTok), tài liệu store lệch manifest. Sửa hết trước khi nộp.
+ - Edge Add-ons có quy trình duyệt riêng; session này chưa đọc chi tiết chính sách Edge, cần kiểm khi chuẩn bị nộp. Cốc Cốc cài từ Chrome Web Store.
+
+
+
4.4. Chống đầu độc dữ liệu
+
Nguyên tắc: mọi bản gửi đều là không tin cậy. Mã extension công khai và chạy trên máy người dùng, ai cũng sửa được hoặc gọi thẳng API. Không có cách nào chứng thực client.
+
+
Lớp công khai (DOS/Rate, ở ingest)
+ - Kiểm định dạng, miền giá trị, host khớp sàn, chặn phụ kiện.
+ - Giới hạn tần suất thô theo IP và theo
submitter_id (chỉ chặn được client trung thực hoặc lỗi; kẻ tấn công đổi mã được).
+ - Chỉ lưu vào vùng cách ly, không chấm điểm, không quyết định.
+
+
Lớp private (DOS/Rate-Services)
+ - Điểm tin cậy người gửi (tuổi của mã tính theo thời điểm server cấp, lịch sử khớp, tài khoản rate.vn nếu có). Mã mới bắt đầu ở mức tin cậy thấp, nên đổi mã liên tục không có lợi.
+ - Đối chiếu với crawler và với người gửi độc lập khác (khác mã người gửi, khác mã băm dải mạng, khác thời điểm).
+ - Phát hiện ngoại lai so với giá gần đây, giới hạn biên độ thay đổi.
+ - Ngưỡng cụ thể, danh sách chặn, trọng số: không công bố.
+
+
+
Cách ly tới khi được xác nhận: một quan sát chỉ lên giá công khai khi khớp với crawler trong biên sai số, hoặc khớp với đủ số người gửi độc lập có điểm tin cậy. Một kẻ tạo hàng nghìn lượt cài giả vẫn phải vượt qua tính độc lập và lịch sử tin cậy.
+
Lưu ý về trần hiện tại: trần 120 job/phút toàn hệ thống trong RPC hiện nay hợp với "yêu cầu cào lại" nhưng không hợp với nguồn chính: vừa là nút thắt, vừa cho phép một kẻ tấn công làm đầy trần để chặn người dùng thật. Phương án B cần một bảng cách ly riêng có giới hạn theo người gửi thay vì trần toàn cục.
+
+
4.5. Khởi động lạnh (cold start)
+
+ - Extension chỉ hữu ích cho người dùng khi đã có lịch sử giá, mà lịch sử giá lại cần người dùng. Crawler phá vòng lặp này: tiếp tục chạy ở các sàn còn cào được để có dữ liệu ban đầu, và làm mốc đối chiếu.
+ - Giả định để ước lượng (chưa có số đo): 1.000 người dùng opt-in, mỗi người xem khoảng 5 trang sản phẩm điện thoại mỗi ngày, cho khoảng 5.000 quan sát/ngày, dồn vào vài trăm mẫu phổ biến. Đủ để làm tươi giá sản phẩm hot, chưa đủ cho đuôi dài. Cần số cài đặt thật để kiểm chứng.
+ - Kênh tăng trưởng: nút cài trên trang sản phẩm rate.vn, giá trị tự thân của extension (phán định giảm giá ảo), Cốc Cốc.
+
+
+
4.6. Độ phủ theo sàn
+
+ | Sàn | Crawler hiện nay (theo PO Rate Services) | Extension thêm được gì | Rủi ro riêng |
+ | Shopee | Chỉ chạy qua một runner đã đăng nhập | Giá trị cao nhất: dữ liệu thật từ nhiều người | Class CSS bị làm rối, đổi thường xuyên (selector hiện có như .pqTWkA); giá phụ thuộc tài khoản nhiều |
+ | Lazada | Search bị trả về trang chặn (RS#659) | Giá trang chi tiết khi người dùng xem | Extension không thay được search discovery |
+ | Tiki | Chưa có báo cáo bị chặn (chưa xác minh) | Chủ yếu để đối chiếu | Thấp |
+ | TikTok Shop | Tạm dừng (RS#658) | Có thể là nguồn duy nhất | Phải thu hẹp quyền host; trang sản phẩm hay mở trong app chứ không trên web, độ phủ có thể thấp |
+ | CellphoneS, TGDĐ, FPT Shop, Hoàng Hà, Di Động Việt, Phong Vũ | Chưa có báo cáo bị chặn (chưa xác minh) | Đối chiếu, làm tươi giá giữa các lượt cào | Giá và tồn kho có thể khác theo tỉnh; đề xuất chỉ lấy giá toàn quốc, bỏ tồn kho theo tỉnh để không phải gửi vị trí |
+
+
+
4.7. Pháp lý và điều khoản sàn
+
+ - Khác biệt then chốt so với crawler: không có request nào của Rate tới sàn; người dùng tự xem trang, extension đọc những gì đã hiện trên màn hình của họ và chỉ gửi khi họ đồng ý.
+ - Điều khoản của nhiều sàn cấm thu thập dữ liệu tự động. Việc đọc thụ động trang người dùng tự mở có thuộc phạm vi đó hay không là vùng xám; Honey, Keepa, BeeCost đã hoạt động nhiều năm, nhưng đó không phải là kết luận pháp lý. Rủi ro thực tế: sàn đổi DOM để làm hỏng extractor, hoặc gửi thư yêu cầu.
+ - Giá và tồn kho là dữ kiện. Đây là hiểu biết chung, chưa có luật sư xác nhận cho luật Việt Nam. Đề xuất xin ý kiến pháp lý trước khi phát hành rộng (mốc M4).
+ - Dữ liệu cá nhân: payload thiết kế để không có; riêng
submitter_id, IP và mã băm dải mạng được xử lý như dữ liệu cá nhân theo Luật 91/2025/QH15 [11].
+ - Câu hỏi cho ý kiến pháp lý: (a) đọc thụ động trang người dùng tự mở có vi phạm điều khoản sàn không; (b) luồng lookup mặc định (gửi URL, tiêu đề, giá của trang đang xem, không cần opt-in) có cần đồng ý riêng theo Luật 91/2025/QH15 không; (c) thời hạn lưu mã người gửi và mã băm dải mạng.
+
+
+
5. Câu hỏi của JOY: vì sao extension công khai còn crawler thì private?
+
+
Trả lời ngắn: extension không phải crawler. Nó chỉ đọc trang người dùng đang xem, khi họ đồng ý, và gửi lên rate.vn. Phần nhạy cảm không nằm ở việc đọc trang, mà nằm ở cách server quyết định tin bản gửi nào. Phần đó private.
+
+
+ - Client buộc phải lộ. Extension được phát cho mọi người; tải file
.crx về giải nén là đọc được toàn bộ mã. Giấu repo không giấu được client, chỉ làm mất lợi ích của open-core: người dùng và người duyệt store kiểm được extension làm đúng điều nó nói. Với một extension đọc trang mua sắm, sự minh bạch này là điểm bán hàng (ngược với các tranh cãi của Honey và Keepa ở mục 2).
+ - Extension không chứa gì thuộc loại crawler. Không tải trang người dùng không mở, không proxy, không giải captcha, không né phát hiện bot, không gọi API nội bộ của sàn. Selector đọc giá trên trang người dùng xem không phải bí mật: ai mở DevTools cũng thấy.
+ - Phía nhận mới là chỗ bị tấn công. Kẻ đầu độc không cần sửa extension, họ gọi thẳng API. Nếu họ đọc được ngưỡng, trọng số, cách đối chiếu với crawler, họ sẽ gửi dữ liệu nằm vừa dưới ngưỡng. Vì vậy chấm điểm tin cậy, đối chiếu crawler, ngưỡng và giới hạn chi tiết nằm ở DOS/Rate-Services.
+ - Giữ bí mật là lớp phòng thủ thêm, không phải lớp duy nhất. Thiết kế vẫn phải đứng vững nếu luật bị lộ: cách ly mặc định, cần xác nhận độc lập, crawler làm mốc. Bí mật chỉ làm việc lách tốn công hơn.
+
+
+
Bảng phân chia sở hữu
+
+ | Thành phần | Repo | Vì sao |
+ | Extension (đọc trang, lời đồng ý, side panel) | DOS/Rate (public) | Phát cho người dùng, đằng nào cũng đọc được; minh bạch giúp qua duyệt store và tạo niềm tin. |
+ Schema payload ExtensionObservation | DOS/Rate (packages/shared-data) | Là hợp đồng giữa client và server; client cần nó, Rate Services dùng qua submodule. |
+ | Ingest API và kiểm tra cơ bản (định dạng, miền giá trị, host, đồng ý, giới hạn thô) | DOS/Rate (apps/web) | Chỉ là kiểm hợp lệ, không có luật tin cậy. Không chấm điểm ở đây. |
+ | Bảng cách ly (lưu quan sát chưa xác minh) | Schema: bản tham chiếu ở DOS/Rate; migration chính thức ở DOS-Me | Cấu trúc bảng không phải bí mật. Anon chỉ ghi qua RPC hẹp, không đọc được. |
+ | Xác minh và chấm điểm tin cậy người gửi | DOS/Rate-Services (private) | Đây là thứ kẻ tấn công muốn đọc. |
+ | Đối chiếu với dữ liệu crawler | DOS/Rate-Services (private) | Gắn với crawler, vốn đã private. |
+ | Ngưỡng, trọng số, danh sách chặn, giới hạn chi tiết | DOS/Rate-Services (private, cấu hình) | Lộ ra là bị gửi vừa dưới ngưỡng. |
+ Đưa quan sát đã xác nhận lên giá công khai (price_history) | DOS/Rate-Services (job ghi bằng role worker) | Chỉ phía private được ghi giá công khai; web chỉ hiển thị kèm nguồn. |
+ | Crawler, proxy, captcha, né bot | DOS/Rate-Services (private) | Như hiện nay, quy tắc cứng của AGENTS.md. Không có phần nào vào extension. |
+
+
+
6. Các phương án
+
+ | Phương án | Mô tả | Được | Mất |
+ | A. Giữ nguyên | Tín hiệu cộng đồng chỉ là yêu cầu cào lại; chỉ sửa các điểm lệch ở mục 1.4. | Rủi ro thấp nhất, tốn ít công (chỉ phần M1). | Không giải quyết tường bot: sàn chặn crawler thì tín hiệu vô dụng. |
+ | B. Quan sát thụ động, cách ly, xác minh (khuyến nghị) | Extension gửi quan sát tối thiểu khi đã đồng ý; Rate Services xác minh chéo rồi mới công khai; crawler làm mốc. | Có dữ liệu ở Shopee, Lazada, TikTok mà không cần né bot; minh bạch; ranh giới repo rõ. | Phụ thuộc số người cài; đuôi dài thưa; cần bảng mới (migration DOS-Me), việc ở cả hai repo; rủi ro duyệt store. |
+ | C. Crawler phân tán trong extension | Server ra lệnh cho extension tải nền trang người dùng không mở (kiểu Keepa bị báo cáo [4]). | Phủ rộng, chủ động. | Là logic cào dữ liệu, không được ở DOS/Rate; dùng IP và phiên của người dùng; nguy cơ bị gỡ khỏi store và mất uy tín. Không khuyến nghị. |
+
+
Bổ sung độc lập với cả ba: nguồn chính thức (API affiliate hoặc đối tác của sàn) nếu có, do Rate Services đánh giá riêng.
+
+
7. Khuyến nghị
+
+
Chọn phương án B, triển khai theo mốc, có giai đoạn chạy ẩn (shadow) để đo trước khi bất kỳ quan sát nào lên giá công khai. Sửa ngay các điểm lệch ở mục 1.4 vì chúng là rủi ro duyệt store kể cả khi giữ phương án A. Rate Services sở hữu ingestion phía sau, xác minh và đưa lên giá công khai.
+
+
+
8. Các mốc
+
+ | Mốc | Nội dung | Repo | Điều kiện qua mốc |
+ | M0 | JOY quyết định về đề xuất này | - | JOY trả lời mục 10 |
+ | M1 | Sửa lời công bố cho khớp payload, mục extension trong /privacy-policy, CHROMEWEBSTORE.md khớp manifest, thu hẹp host TikTok, màn hình chào opt-in | DOS/Rate | Review fresh-context, kiểm bằng tay trên 10 sàn |
+ | M2 | Schema ExtensionObservation v1, extractor đọc tồn kho và SKU, ingest lưu vào bảng cách ly mới (migration qua DOS-Me, JOY duyệt) | DOS/Rate, DOS-Me | Không ghi gì vào giá công khai; test ingest đầy đủ |
+ | M3 | Xác minh và chấm điểm ở chế độ shadow: chấm nhưng không công khai, 2 đến 4 tuần | DOS/Rate-Services | Các chỉ số ở mục 9 đạt ngưỡng đề xuất |
+ | M4 | Đưa quan sát đã xác nhận lên price_history kèm nguồn; web hiện "nguồn: người dùng, đã đối chiếu"; ý kiến pháp lý; xác nhận Limited Use trong developer dashboard; nộp Chrome Web Store và Edge | Cả hai | JOY duyệt bước production và bước nộp store |
+
+
+
9. Chỉ số quyết định thành công
+
+ | Chỉ số | Ngưỡng đề xuất (để JOY chỉnh) |
+ | Độ khớp với crawler trên listing cả hai cùng có (shadow) | Ít nhất 98% quan sát lệch không quá 1% |
+ | Độ khớp giữa người gửi độc lập trên listing crawler không vào được (TikTok, Shopee và Lazada bị chặn), cộng một mẫu kiểm tra bằng tay mỗi tuần | Ít nhất 95% cặp quan sát cùng giờ lệch không quá 1%; mẫu tay khớp từ 95% |
+ | Tỷ lệ quan sát đã lên công khai mà sau đó bị chứng minh sai (bị crawler hoặc mẫu kiểm tay bác trong vòng 24 giờ, với giá lệch quá 1%) | Dưới 0,5% |
+ | Tỷ lệ opt-in trên người dùng hoạt động | Đo trước, đặt ngưỡng sau M2 |
+ | Phần trăm listing Shopee, Lazada, TikTok trong catalog có giá tươi dưới 24 giờ | Tăng rõ so với chỉ crawler (đo mốc trước M3) |
+ | Tuổi trung vị của giá đang hiển thị | Giảm so với mốc trước M3 |
+ | Bị từ chối hoặc gỡ trên store; khiếu nại quyền riêng tư | 0 |
+
+
+
10. Câu hỏi mở cho JOY
+
1. Chọn phương án nào? Khuyến nghị B. A nếu muốn chờ thêm; C không khuyến nghị.
+
2. Mặc định đồng ý: giữ tắt và hỏi rõ ở màn hình chào (khuyến nghị), hay chỉ để công tắc trong side panel như hiện nay?
+
3. Danh tính người gửi: chỉ mã ẩn danh theo lượt cài, hay khuyến khích đăng nhập rate.vn để bản gửi có trọng số cao hơn?
+
4. Listing chỉ một người thấy và crawler không vào được: ẩn tới khi có xác nhận (khuyến nghị), hay hiện với nhãn "chưa xác minh"?
+
5. Ý kiến pháp lý trước khi phát hành rộng: có thuê luật sư không, ngân sách bao nhiêu?
+
6. Phát hành store: dưới tài khoản nhà phát triển nào, và có nộp Edge cùng lúc với Chrome không?
+
7. Giữ số sao và lượt đánh giá trong payload, hay bỏ để payload chỉ còn giá, tồn kho, SKU (khuyến nghị bỏ)?
+
+
Nguồn
+
+ - Honey, "Data and privacy": joinhoney.com/data-and-privacy
+ - datarequests.org (2020), về việc Honey thu thập dữ liệu: datarequests.org/blog/honey-data-collection
+ - Keepa trên Firefox Add-ons: addons.mozilla.org/en-US/firefox/addon/keepa
+ - Wladimir Palant, "Data exfiltration in Keepa Price Tracker" (2021): palant.info
+ - Chromium Extensions group, "Keepa extension is deceptively scraping Amazon in the background": groups.google.com.
[5b] Thư viện Python cho Keepa API: pypi.org/project/keepa
+ - Chrome Web Store, User Data FAQ: developer.chrome.com
+ - mytour.vn, về tính năng BeeCost: mytour.vn
+ - uplus, review extension BeeCost: uplus.substack.com
+ - BeeCost trên Ameblo: ameblo.jp/beecost
+ - GHN, "BeeCost Shopee là gì": ghn.vn
+ - Cổng thông tin Hà Tĩnh, Luật Bảo vệ dữ liệu cá nhân có hiệu lực từ 01/01/2026: hatinh.gov.vn
+ - Thư viện Pháp luật, Nghị định 356/2025/NĐ-CP (chưa đối chiếu với Công báo; cần luật sư xác nhận ngày ban hành và hiệu lực): thuvienphapluat.vn
+
+
Mã nguồn đã đọc (DOS/Rate): apps/extension/manifest.json, apps/extension/CHROMEWEBSTORE.md, apps/extension/src/background/service-worker.ts, apps/extension/src/extractors/ecommerce-detector.ts, apps/extension/src/sidepanel/sidepanel.html, apps/web/src/app/api/v1/rate/extension/ingest/route.ts, apps/web/src/server/rate/extension.ts, apps/web/src/app/[locale]/privacy-policy/page.tsx, apps/web/locales/*.json, supabase/migrations/20261018120101_rate_crowd_verify_signal_rpc.sql. DOS/Rate-Services chỉ được đọc để hiểu chung, không trích mã.
+
+
+
diff --git a/Docs/Proposals/2026-10-28-extension-data-source.md b/Docs/Proposals/2026-10-28-extension-data-source.md
new file mode 100644
index 00000000..c9ab698f
--- /dev/null
+++ b/Docs/Proposals/2026-10-28-extension-data-source.md
@@ -0,0 +1,96 @@
+# Proposal: the browser extension as a primary data source
+
+- **Date:** 2026-10-28
+- **Issue:** [DOS/Rate#1175](https://github.com/DOS/Rate/issues/1175)
+- **Status:** PROPOSAL, awaiting JOY's decisions. No product code has changed.
+- **Full decision document (Vietnamese):** [`2026-10-28-extension-data-source.html`](./2026-10-28-extension-data-source.html)
+
+## Summary
+
+The private crawlers hit bot walls. TikTok is paused (DOS/Rate-Services#658), Lazada search returns a punish page (DOS/Rate-Services#659), and Shopee works only through one logged-in runner. A real user's browser does not hit these walls, because the user opened the page.
+
+The recommendation is **Option B**. With consent, the extension sends a minimal observation: price, stock, SKU, listing URL, timestamp and marketplace, and no personal data. The observation goes into a quarantine table. It becomes a public price only after DOS/Rate-Services corroborates it against the crawler or against independent submitters. The crawler stays as the seed and the cross-check.
+
+The extension never fetches pages the user did not open, never evades bot detection and never solves captchas.
+
+## Current state (read from `dev`)
+
+- **Lookup:** on every product page, with no opt-in, the extension sends `GET /api/v1/rate/extension/lookup` with the URL, title, price, item id and shop id. This path is read-only.
+- **Ingest:** with the opt-in on (off by default), the extension sends `POST /api/v1/rate/extension/ingest`. The route checks the payload with Zod and allows 15 requests per minute per IP. That limiter is in memory per serverless instance, not global. It then calls the `rate.enqueue_crowd_verify_signal` RPC, which enqueues an `extension_crowd_verify` job in `rate.crawl_jobs`. The RPC caps all crowd jobs at 120 per minute; this is the only global limit.
+- **Quarantine today:** from a read of the Rate-Services code, the worker has no separate handler for this job type and re-crawls the URL like any other job. The price the user saw is not used as data, so the crowd signal does not help when the crawler is blocked.
+- **Mismatches to fix under any option:**
+ - The opt-in label says only the cleaned URL is sent. The payload actually has ten fields: platform, item id, shop id, URL, title, variant, price, original price, rating and rating count.
+ - `CHROMEWEBSTORE.md` justifies `tabs` and `localhost`, which the manifest does not request. It omits five hosts that the manifest does request: TikTok, Hoang Ha, Di Dong Viet, Phong Vu and `www.rate.vn`. Its privacy section says the extension "only activates" on product pages of 6 domains, while the content script matches every path on 10 sites.
+ - The default lookup sends browsing data without opt-in. That is probably allowed for a user-facing feature if it is disclosed, but this is an assumption and belongs in the legal review.
+ - `*.tiktok.com` is broader than product pages need.
+ - `/privacy-policy` does not mention the extension.
+ - The extractor does not read stock.
+
+## Comparable products (public sources only)
+
+- **Honey:** Honey says it collects general product data such as price and availability, only on supported stores, to power price history and Droplist ([joinhoney.com](https://www.joinhoney.com/data-and-privacy/)). datarequests.org says Honey collects more browsing data than its policy states ([datarequests.org](https://datarequests.org/blog/honey-data-collection)).
+- **Keepa:** Keepa says its add-on sends the ASIN of the product the user is viewing ([AMO](https://addons.mozilla.org/en-US/firefox/addon/keepa/)). In 2021, Wladimir Palant reported that the server instructs the extension to load, in the background, Amazon pages the user never opened ([palant.info](https://palant.info/2021/08/02/data-exfiltration-in-keepa-price-tracker/), [Chromium group](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/il9VqQBc8So)). Rate must not do this.
+- **BeeCost:** BeeCost has extensions for Chrome and Coc Coc that show price history for Shopee, Tiki, Lazada and Sendo ([mytour.vn](https://mytour.vn/en/blog/bai-viet/top-5-features-of-beecost-the-ultimate-tool-for-finding-the-best-online-shopping-deals-mytour.html)). The developer claims over 100 million products ([ameblo.jp](https://ameblo.jp/beecost/)). No public document describes how it collects data.
+
+## Anti-poisoning, in short
+
+- Every submission is untrusted, because the client is open and the API can be called directly.
+- The server issues `submitter_id` when the user consents and records the first-seen time itself. A new id starts with low trust, so rotating ids gains nothing. The per-id limit only bounds honest clients.
+- The raw IP is not stored. Rate-Services keeps a short-lived, salted hash of the network range (for example /24 or ASN) to test independence. That hash is personal data and is disclosed.
+- Observations stay quarantined until they match the crawler or enough independent, trusted submitters.
+
+## Why the extension is public while crawling is private
+
+The extension is not a crawler. It ships to every user and can be unpacked, so a private repo would not hide it. Being open lets users and store reviewers check that it does what it says.
+
+The sensitive part is how the server decides which submissions to trust. An attacker would call the API directly and would want to know the thresholds. That logic lives in DOS/Rate-Services. Secrecy is defense in depth only: the design must still hold if the rules leak, through default quarantine, independent corroboration and the crawler baseline.
+
+| Piece | Repo |
+| :----------------------------------------------------------------------------- | :------------------------------------------------------- |
+| Extension: page reading, consent UI, side panel | DOS/Rate (public) |
+| Payload schema `ExtensionObservation` | DOS/Rate, `packages/shared-data` |
+| Ingest API and basic validation (format, ranges, host, consent, coarse limits) | DOS/Rate, `apps/web` |
+| Quarantine table | Reference copy in DOS/Rate; official migration in DOS-Me |
+| Verification and submitter trust scoring | DOS/Rate-Services (private) |
+| Crawler cross-check | DOS/Rate-Services (private) |
+| Thresholds, weights, blocklists, detailed limits | DOS/Rate-Services (private config) |
+| Promotion of observations to public `price_history` | DOS/Rate-Services (worker role) |
+| Crawlers, proxy, captcha, bot evasion | DOS/Rate-Services (private); never in the extension |
+
+## Options
+
+| Option | Description | Verdict |
+| :------------------------------------------------------ | :------------------------------------------------------------ | :------------------------------------------------------------------------------------------------ |
+| A. Keep as is | Fix the mismatches only. | Safe, but does not solve the bot walls. |
+| B. Passive observation with quarantine and verification | Described above. | **Recommended.** |
+| C. Distributed crawler in the extension | The server tells extensions to fetch pages in the background. | Rejected. This is scraper logic, it misuses users' IPs and sessions, and it risks store takedown. |
+
+An official source (a marketplace affiliate or partner API), where one exists, can complement any option. Rate-Services would assess it separately.
+
+## Milestones
+
+| Milestone | Work | Repo |
+| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------- |
+| M0 | JOY decides. | - |
+| M1 | Fix the disclosure copy, the privacy policy, `CHROMEWEBSTORE.md` drift and the TikTok host scope. Add an onboarding opt-in screen. | DOS/Rate |
+| M2 | Add the `ExtensionObservation v1` schema. The extractor reads stock and SKU. Ingest writes to a new quarantine table through a DOS-Me migration, with JOY's approval. Nothing is published. | DOS/Rate, DOS-Me |
+| M3 | Run verification in shadow mode for 2 to 4 weeks: score observations but publish nothing. | DOS/Rate-Services |
+| M4 | Promote corroborated observations with provenance. Get a legal opinion. Certify Limited Use in the developer dashboard. Submit to the Chrome Web Store and Edge Add-ons. | Both |
+
+**Proposed success thresholds:**
+
+- On listings both sources cover, at least 98% of shadow observations within 1% of the crawler.
+- On listings the crawler cannot reach (TikTok, blocked Shopee and Lazada), at least 95% of same-hour pairs from independent submitters within 1%, plus a weekly manual spot-check sample.
+- Under 0.5% of promoted observations later proven wrong, meaning the crawler or a manual check contradicts them by more than 1% within 24 hours.
+- Better price freshness on Shopee, Lazada and TikTok than with the crawler alone.
+- Zero store rejections and zero privacy complaints.
+
+## Open questions for JOY
+
+1. Which option: A, B or C?
+2. Should the opt-in stay a side-panel toggle, or also be asked on an onboarding screen?
+3. Should submitters stay anonymous per install, or should a rate.vn sign-in add weight?
+4. Should a listing seen by one user only, which the crawler cannot reach, stay hidden or show as unverified?
+5. Should we get a legal opinion before wide release, and with what budget?
+6. Which store developer account should publish, and should Edge ship together with Chrome?
+7. Should ratings stay in the payload?