Skip to content

chore(release): promote dev to main - #1613

Merged
JOY (JOY) merged 7 commits into
mainfrom
dev
Nov 6, 2026
Merged

JOY (JOY) merged 7 commits into
mainfrom
dev

Conversation

@JOY

@JOY JOY (JOY) commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Promote dev to main.

Merge with a merge commit, never squash (Docs/Resources/RELEASE.md).


📌 TL;DR

This PR introduces the M2 database migration and documentation for the "extension observation quarantine," establishing a secure, rate-limited ingestion pipeline for browser extension data with strict privacy controls. It also updates the AI review proposal to reflect a pivot from Anthropic Claude to the internal dos.ai Batch API.

🎯 Type of Change

  • 🚀 New feature
  • 🐛 Bugfix
  • 🧹 Refactor
  • ⚡ Performance
  • 📚 Documentation
  • ⚙️ CI / Configuration

🔍 Changes Walkthrough

File Summary of Changes
.github/workflows/ci.yml Adds a CI step to replay the new extension observations proposal in Docker to verify migration integrity.
.gitignore Whitelists the new SQL proposal, rollback, and verify files so they are tracked in the repository.
.gitleaksignore Adds a false-positive ignore for a test fixture containing a fake API key in write-paths.test.ts.
Docs/Proposals/2026-11-03-ai-review-points.html Updates the AI review proposal to document the switch from Anthropic Claude to dos.ai (Qwen/DeepSeek models) via the Batch API, including new cost estimates and API key changes.
Docs/Proposals/2026-11-04-dos-me-extension-observations.md New design document detailing the M2 quarantine architecture, privacy rules, rate limits, and application runbook.
Docs/Proposals/2026-11-04-dos-me-extension-observations.sql Core Migration: Creates extension_ingest_secret, extension_submitters, and extension_observations tables. Defines strict RLS policies, column-level grants, and four SECURITY DEFINER functions for registration, submission, deletion, and purging.
Docs/Proposals/2026-11-04-dos-me-extension-observations-verify.sql Read-only verification script (V1-V13) to validate schema structure, permissions, RLS policies, and function security post-migration.
Docs/Proposals/2026-11-04-dos-me-extension-observations-rollback.sql Safe rollback script to drop the new functions and tables in a single transaction.

📊 Architectural Flow

sequenceDiagram
    participant Ext as Browser Extension
    participant Web as Web API (Vercel)
    participant DB as PostgreSQL (Supabase)
    participant Cron as Daily Purge Job

    Note over Ext,Web: Ingest Key & Network Hash Validation
    Ext->>Web: POST /api/extension/submit (Key, Observation, NetHash)
    Web->>Web: Validate Payload (Shared Validator)
    Web->>DB: RPC: rate.submit_extension_observation()
    
    alt Key Invalid or Rate Limit Exceeded
        DB-->>Web: Error (42501 or 54000)
        Web-->>Ext: 401 or 429
    else Valid
        DB->>DB: Insert into rate.extension_observations (Status: quarantined)
        DB-->>Web: {id, status, received_at}
        Web-->>Ext: 200 OK
    end

    Note over Cron,DB: Privacy & Retention (Daily)
    Cron->>DB: RPC: rate.purge_extension_observations()
    DB->>DB: Clear network_hash (>28 days)
    DB->>DB: Delete observations (>180 days)
    DB->>DB: Delete empty submitters (>7 days)
Loading

JOY (JOY) and others added 7 commits November 4, 2026 12:32
* feat(extension): onboarding consent screen for the opt-in

On a first install the extension opens a short consent page (vi, en,
cs): what the lookup sends, what the opt-in would add (the same field
list as the side panel), that nothing more is sent until the user says
yes, and that the side panel can change it. "No" has the focus and the
primary style; closing the page keeps the default, off.

The choice goes through RATE_UPDATE_SETTINGS to the same setting as the
side panel toggle, which now follows changes made elsewhere. The sender
guard accepts a tab sender only when it is this extension's consent page
URL, exactly; content scripts stay refused. Updates and reloads do not
open the page.

Refs #1603, #1175

* fix(extension): consent page sets only the opt-in, defaults first

- isOnboardingSender accepts the exact consent page URL and lets it set
  the opt-in alone; isPrivilegedExtensionSender refuses tab senders again.
- onInstalled writes each missing default before opening the page, so a
  quick choice is never overwritten; a stored choice is kept.
- The page status follows the stored choice.

Refs #1603

* fix(extension): consent copy, choice state and docs after review

- The consent page has its own opt-in text (onboardingOptInBody): the
  same fields as the side panel, tested, plus the server re-check and
  "no account, name, email or cookies". The lookup line lists every
  field lookup sends.
- "Off by default" and how to reach the side panel toggle replace the
  vague line; vi, en and cs say the same.
- Keyboard focus stays on the clicked button, the status is announced
  again on a repeat, and aria-pressed marks the stored choice.
- The privacy policy and CHROMEWEBSTORE.md name the consent page;
  RELEASE.md has a test step and changelog line for it.

Refs #1603

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gateway (#1607)

* feat(importer): review ai points job calls models through the dos.ai gateway

JOY decided on 2026-11-04 that the job uses dos.ai, not the Anthropic API
(D2 of the AI review points design). The job now sends its batch through
the gateway's OpenAI-compatible Files and Batch API with DOS_AI_API_KEY
(DOS_AI_BASE_URL overrides the URL). That Batch API serves dos, qwen3.8
and deepseek models only, so the default model is dos (Qwen3.8 27B on DOS
GPUs) and qwen3.8-max is the fallback, with the same monthly caps.

Caps, kill switch, process-once, evidence checks and quality stops are
unchanged. A cut-off or filtered answer (finish_reason length or
content_filter) and an errored line are stored as failed. Cached tokens
count at the full input price, an upper bound. @anthropic-ai/sdk is
removed. The proposal notes the D2 change.

Refs #1531

* fix(importer): dos.ai batch client never creates a batch twice

Review findings on the dos.ai client (#1531):
- POST /batches is retried only on 429; a lost answer throws and says a
  batch may exist, instead of creating and billing a second one.
- A batch failed as a whole, one with no output, or one whose output file
  is gone (404) closes its row and stores nothing, so its reviews are due
  again without spending an attempt; a line not run for want of credits
  is unbilled, not failed.
- Each call's timeout covers reading the body.
- Tests: create-once on a network error and a 502, retry on 429, failed
  and purged batches, 402 lines, the request body; batch ids renamed.
- The proposal notes the dos.ai batch path in sections 4 and 5.

Refs #1531

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ingest (#1609)

* feat(extension,web): extension observation v1, stock and sku, flagged ingest

- shared-data: ExtensionObservation v1 in rules, plain functions with no
  zod. The parser names the first broken field the way the DOS-Me RPC
  does in DETAIL. No ratings, all keys required, listing host must match
  the marketplace, and observed_at must be within the last 24 hours (up
  to 10 minutes ahead).
- Extension: the extractor reads stock state and quantity (JSON-LD
  availability and inventoryLevel, product meta) and the SKU (Lazada and
  Tiki URLs, JSON-LD sku). buildExtensionObservationV1 maps a signal to
  v1. Nothing new is sent: the lookup and the opt-in payload are
  unchanged, and a test pins that.
- Web: POST and DELETE /api/v1/rate/extension/submitter and POST
  /api/v1/rate/extension/observation call the narrow M2 RPCs as anon.
  They answer 404 NOT_ENABLED unless RATE_EXTENSION_OBSERVATIONS=1, and
  observations also need RATE_NETWORK_HASH_SALT. Only an HMAC of the /24
  or /48 reaches the database, never the raw IP.

Refs #1606, #1175

* fix(extension,web): m2 review fixes, ingest key and variant-true stock

Web:
- The RPCs now take RATE_EXTENSION_INGEST_KEY (checked by sha256 in the
  database), so nobody can call them around these routes with the anon
  key. Writes need the key and the salt, else 503 before the database.
- Register sends the network hash too. An address with no range hashes
  to one shared "unknown" bucket instead of skipping the limit.
  IPv4-mapped IPv6 maps to its /24.
- Web page origins are refused (403); CORS goes only to extension
  origins; bodies must be application/json (415). OPTIONS is 404 while
  off. RPC refusals log their code; 54000 answers Retry-After 3600;
  42501 is a 503.

Shared rules: listing_url and observed_at follow the RPC exactly (raw
https prefix, lowercase host, no port, user, ';' or space; ISO 8601
with a zone, offsets as the RPC takes them); lengths count code points;
unpaired surrogates are refused.

Extension:
- Stock and SKU come from the selected variant's own offer: the only
  offer, or the one whose sku or name matches the selected variant;
  never another variant's or the parent's when several exist. The meta
  fallback reads head meta only.
- Availability: schema: prefixes, arrays, Reserved and pre-order forms.
  A quantity is kept only when it agrees with the state.
- extractorSource follows price, list price and stock. A long variant
  label is cut to 120 for v1. skuId uses the external id sanitizer.
- buildLookupParams carries the lookup query; tests pin the exact
  lookup query and opt-in payload, and cover a page end to end.

Refs #1606

* fix(web): observed_at bounds match postgres, brake retry-after

- observed_at refuses seconds of 60 and offsets beyond +-15:59, which
  Postgres refuses, so the validator and the RPC agree.
- A 54000 from the global register brake answers Retry-After 60; the
  hourly limits keep 3600.

Refs #1606

* fix(shared-data,web): host check without a built regex, test key at run time

- isExtensionMarketplaceHost checks subdomains by suffix and a fixed
  label pattern instead of a RegExp built from the host table (CodeQL
  incomplete escaping).
- The ingest key test value is built at run time; the already-pushed
  fixture line in d19083a gets a .gitleaksignore fingerprint.

Refs #1606

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(proposals): dos-me extension observation quarantine (m2)

Draft DOS-Me migration for ExtensionObservation v1 (#1606). Nothing is applied.

- rate.extension_submitters and rate.extension_observations, RLS on, no anon or
  authenticated table access, rate_worker reads and updates status only.
- register, submit, delete and purge RPCs, security definer with an empty search_path,
  with the contract's SQLSTATEs and DETAILs, limits and 28/180 day retention.
- Verify and rollback files, and a Docker replay that exercises every RPC, the grants,
  the rollback and a re-apply.

Refs #1606

* docs(proposals): extension observations revision 2, ingest key and limits

Applies Revision 2 of the M2 contract (#1606). Nothing is applied.

- rate.extension_ingest_secret: register and submit check the sha256 of an ingest key
  (42501, DETAIL ingest_key); the network hash is required in both.
- Register: 20 an hour per network hash, then the global brake under an advisory lock;
  submitters keep their network hash.
- Purge: empty submitters after 7 days, locked first and deleted under a fresh check.
- DETAIL observation for an unknown key; presence checked per field in the validator order;
  semicolons and JavaScript spaces refused in listing_url.
- Verify: owner, no forced RLS, column list, limits in the bodies, no grants on the key.
- Replay: parity with parseExtensionObservationV1, exact thresholds, ingest key, the purge
  race; wired into CI after the shared-data build.

Refs #1606

* fix(proposals): extension observations purge lock order and key checks

Re-review fixes for the M2 quarantine proposal (#1606). Nothing is applied.

- Purge clears submitter hashes before observation hashes, the lock order of delete, so a
  delete during a purge waits instead of deadlocking. The replay stalls a purge, runs a delete
  in the middle, and fails with the old order.
- The key row cannot hold the sha256 of the empty string, and register and submit refuse a
  missing key or one under 32 characters (42501, DETAIL ingest_key).
- observed_at: seconds 00 to 59 and offsets up to 15:59, as Postgres parses them.
- The .md runbook is written for the applying session: CSPRNG key, printf hash, Vercel
  secrets, apply, row, verify, then the flag; rotation is a hard cut.

Refs #1606

* Merge remote-tracking branch 'origin/dev' into docs/1606-dos-me-extension-observations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…res (#1610)

* feat(extension): opted-in installs send observation v1, with disclosures

The opt-in now sends ExtensionObservation v1 to the observation route
with a random submitter id from the submitter route, instead of the
legacy crowd ingest (no title, no ratings). Turning the opt-in off
deletes the id and every observation sent with it. The web routes stay
off (RATE_EXTENSION_OBSERVATIONS), so nothing is stored until JOY turns
them on.

The opt-in text, the consent page, the privacy policy (vi, en, cs),
CHROMEWEBSTORE.md and the changelog name the v1 fields, the submitter id
and the salted network-range hash with its retention; a test ties the
texts to the v1 keys.

Refs #1606

* fix(extension): observation client with kept erasure, holds and consent v2

- observation-client.ts runs register, send and erase on one chain: two
  product pages register one id, and an opt-out waits for a send in
  flight; an id issued after the opt-out is erased at once.
- Erasures stay queued until the server confirms (200) and are retried
  at start-up and on the next send; the stored id carries the server
  that issued it, and an id from another server is erased there.
- 429 holds for its Retry-After; routes off (404 NOT_ENABLED) or 503
  hold an hour, so opted-in installs stop asking.
- An opt-in given to the older crowd signal is not consent to v1: it
  reads as off, and an update reopens the consent page.
- Disclosures: stock quantity, the server-derived network hash, the IP
  sentence, one name for the contributor ID, the uninstall case, and a
  release test step for erasure.
- Behavioural tests for the client with stubbed storage and fetch.

Refs #1606

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1611)

* chore(db): mirror the m2 extension observation migrations from dos-me

DOS/DOS.Me#1560 (quarantine and key row), #1564 (daily purge schedule) and
#1569 (key row rotation) carry #1606. The quarantine and the
purge schedule are on production through #1561 and #1568; #1569 is merged
on dev. Mirror the four migrations byte for byte into supabase/migrations
and regenerate packages/shared-data/src/database.types.ts: the three
extension tables and the register, submit, delete and purge functions.

The extension observations proposal replay now rolls the mirrored
quarantine back first, so it still replays from the stack before it.

Nothing is applied by this change.

Refs #1606

* chore(db): follow the ingest key rotation to version 20261109090000

DOS/DOS.Me#1578 moves the rotation off 20261107100000, which two other
DOS.Me migrations also used. Mirror the renamed file byte for byte.

Refs #1606

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore(db): mirror the four older drift migrations from dos-me

DOS/DOS.Me is the only deployable ledger. Four rate migrations differed
between it and supabase/migrations:

- 20261013192302_jreviews_rating_and_review_flags.sql and
  20261014011054_review_votes.sql: the DOS.Me version is the one on
  production (no explanatory comments; review_flags policies created
  only when missing). Replace the reference copies with it.
- 20261025090000_rate_product_images_bucket.sql: only in DOS.Me. It
  needs the Supabase storage schema, so the replay skips it, as DOS.Me's
  own replay does (NOT_REPLAYED in scripts/lib/rate-db.mjs).
- 20261106150000_rate_listing_price_context_peer_max_history_first.sql:
  only in DOS.Me (Rate-Services price context). Mirrored, and
  database.types.ts regenerated: history_first_at and peer_reference_max.

Nothing is applied by this change.

Refs #1606

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@vercel

vercel Bot commented Nov 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
rate Ready Ready Preview Nov 6, 2026 4:24am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c375b0ae-4c12-498b-83eb-5d3b4d925292

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions github-actions Bot added ci CI/CD workflows and pipelines docs Documentation changes area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs area:crawlers area:database area:extension labels Nov 6, 2026
dos[bot]
dos Bot approved these changes Nov 6, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⏱️ Code Review completed (50 eligible files · 382,866 chars · 3 model call(s) · 13 PR unit(s))

Whole diff with bounded context at base 8919776 / head 1ba7325.
Excluded by noise policy: pnpm-lock.yaml.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

🔒 Dependency check

✅ Passed. Every new package version comes from the public registry or a source this lockfile already used for it, its checksum is pinned where the format records one, and OSV lists no new malicious package or high or critical vulnerability for the versions it was asked about.

@JOY
JOY (JOY) merged commit 78cc446 into main Nov 6, 2026
27 checks passed

This branch was successfully deployed

1 active deployment
Preview — 1ba73251 Deployed Nov 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:crawlers area:database area:extension area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs ci CI/CD workflows and pipelines docs Documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant