Start here if you're a fresh agent picking up the DMV project after the cross-repo API hardening arc. This is the current production snapshot: both registration and certificate lookup are live. The rest of the repo's docs (CLAUDE.md, AUTH_DMV.md, ARCHITECTURE.md, CLOUDFLARE.md, README.md, SECURITY.md) record the production boundary and recovery rules. The cross-repo hardening work landed 2026-08-01 — see the four Task entries below.
registrations.metadata.client_ip was storing the raw client IP in the shared production DB that PAGE also reads, violating the hash-only invariant the Worker already follows everywhere via sha256Hex. Fixed in supabase/functions/register-agent/index.ts: the metadata key is now client_ip_hash (SHA-256 hex via Deno crypto.subtle), and the raw client_ip key is dropped entirely. Both repos were grepped for readers of metadata->>'client_ip' — none found, so dropping the raw value was safe; the hash keeps abuse-triage utility. handleRegisterAgent(req, dependencies) is now exported (mirroring lookup-agent/index.ts's pattern) so the function is unit-testable; HTTP-boundary behavior is unchanged. New test coverage: supabase/functions/register-agent/index.test.ts, asserting inserted metadata contains client_ip_hash matching /^[0-9a-f]{64}$/ and never a raw client_ip key, across x-forwarded-for, cf-connecting-ip, and no-IP cases. DEPLOYED 2026-08-01 (after #23 merged) — register-agent is live at version 38, ACTIVE, verify_jwt=false (npx supabase@latest functions deploy register-agent --project-ref tcymqfwwphacnosnnzxl --no-verify-jwt, SUPABASE_ACCESS_TOKEN auth). New registrations now store client_ip_hash and read status='complete' for the endorsed cap. Existing rows with raw metadata.client_ip are untouched; a backfill/strip of those historical values remains a separate decision (needs pgcrypto confirmed for in-SQL hashing). This also covers the Task 2 cap-predicate fix (same file, same deploy).
The per-email lifetime cap upgrade (5 unendorsed → 12 endorsed) was reading registrations.endorsement_status = 'signed' to decide whether an email qualified for the higher cap. That column is dead on the shared PAGE DB — signing truth there is registrations.status = 'complete' (see PAGE's docs/SUPABASE.md and CLAUDE.md). Members who signed after endorsement_status stopped being written were silently stuck at the 5-cert cap. Fixed in supabase/functions/register-agent/index.ts to check status = 'complete'; the 5/12 cap values themselves are unchanged, and the query stays fail-closed on error. Deliberately does not also check endorsement_requests — that table has no reliable email column for this lookup (only registration_id and an unreliable signer_email), and this check is keyed by email with no user_id/registration_id available, so status='complete' on registrations already answers it in one query. New tests in supabase/functions/register-agent/index.test.ts.
The former Workers KV read-then-write cooldown was not atomic: simultaneous
requests could all observe spare capacity and mint more than the documented
three certificates. This branch replaces it with one SQLite Durable Object per SHA-256
machine-fingerprint hash (REGISTER_FINGERPRINT_LIMITER, forward-only v3
migration). A transaction reserves a claim before upstream work, so pending
claims and committed successes jointly occupy the three slots. A well-formed
fresh 201 commits; only audited, well-formed pre-INSERT 400/403/409
responses and exact 200 already_recorded replays release. Every 5xx/546,
malformed/unexpected response, body-read failure, timeout/abort, and transport
failure stays pending. The Worker's 45-second local response timeout does not
claim to cancel Supabase execution. If COMPLETE never arrives, the claim stays
pending for a conservative 600-second horizon: Supabase's documented 150-second
request-idle timeout plus its 400-second Edge Function wall clock plus a
50-second safety margin. It then becomes a possible success and remains counted
for a full rolling 24 hours from the horizon timestamp. This avoids reopening a fourth mint while still
recovering automatically. Durable Object failure fails closed with a generic 503; public
responses never expose claim IDs, raw fingerprints, or raw IPs. Concurrent,
abandoned-claim, duplicate-token, malformed-token, release, and rollover
regressions live in tests/worker-registration-fingerprint-rate-limiter.test.ts
and tests/worker-register-fingerprint-cooldown.test.ts.
Production v3 status (2026-08-02): REGISTER_FINGERPRINT_LIMITER is live
after an accidental non-main branch promotion to production. Cloudflare's
dashboard commands are corrected: production uses npx wrangler deploy, and
non-production uses npx wrangler versions upload. The repository deploy guard
uses WORKERS_CI_BRANCH as a second layer. Because the v3 migration reached
production, every future deployment and recovery source must preserve v1/v2/v3
even though the promotion was accidental. The April browser signup remains
evidence for the older registration path, not a v3 registration smoke record.
Code review across both agentcommunity_PAGE and this repo found that the
@agentcommunity/dmv-agent npm package's MCP server exposed a tool named
verify_certificate that only checked the Luhn mod-36 check digit
(packages/dmv-agent/src/certificate.ts), while agentcommunity_PAGE's
/mcp endpoint exposes a tool of the same name that checks live issuance
via GET /api/lookup on this Worker. An agent with both MCP servers
configured could ask "is CERT-ID valid?" and get a "yes" from one server and
a "no, not found" from the other, for the same ID.
Decision: Option A (wire the package to the real lookup), not Option B
(rename). The package's verify_certificate (MCP) and dmv-agent verify
(CLI) now call GET https://dmv.agentcommunity.org/api/lookup?id=<id> by
default — the same public, rate-limited (~30/60s per IP) Worker route the
main site's tool uses — via the new packages/dmv-agent/src/lookup.ts. A
format_only: true MCP argument / --format-only CLI flag preserves the old
check-digit-only behavior with no network call. Network errors, timeouts,
unexpected HTTP statuses, and malformed, partial, extra-field, mismatched-ID,
or otherwise inconsistent JSON fall back to the offline check digit and label the result
(format-only ...) in its text output; a network failure is never reported
as "not issued". An exact typed HTTP 503 unavailable response and an exact
HTTP 429 rate-limit response remain live but inconclusive. Redirects are
manual and never followed. Full detail in packages/dmv-agent/CHANGELOG.md (0.3.0)
and packages/dmv-agent/README.md.
The lookup boundary is live. Nothing was published to npm as part of this
source change: the published packages remain
@agentcommunity/dmv-agent@0.2.2 and the compatibility alias
dmv-agent@0.1.2. The canonical source manifest is 0.3.0, the compatibility
alias source is 0.1.3, and publication remains an explicit package-owner
action after the release gates pass. The source now includes a GitHub-hosted
OIDC workflow and reproducibility verifier, but npm trusted-publisher
configuration and workflow dispatch have not occurred. No provenance is
claimed for the current 0.2.2/0.1.2 releases.
The release workflow now confines OIDC to two main-only, protected
npm-production publish jobs; candidate code runs only in unprivileged jobs.
It resumes through absent-or-exact canonical and alias states, cryptographically
verifies provenance with npm audit signatures, and requires a version bump on
any mismatch or ambiguous state. The default package verifier performs no
production operation. Its explicit non-registration --production-smoke does
consume live quota and may populate caches. External owner setup for both npm
trusted publishers and the protected GitHub environment remains outstanding.
Production was re-verified on 2026-08-01: normal requests return real issued
or not_found results, while unavailable remains reserved for genuine
failures.
DMV worker https://dmv.agentcommunity.org /api/register live end-to-end
Turnstile + shared CF rate limits + v3 exact fingerprint DO
v3 live as of 2026-08-02; preserve v1/v2/v3 forward-only
v3 source this branch exact REGISTER_FINGERPRINT_LIMITER live
separate registration smoke evidence still required
DMV edge function register-agent on tcymqfwwphacnosnnzxl x-dmv-proxy gate active (DMV_PROXY_SECRET shared secret;
public v1 constant retired 2026-05-29, now 403)
Upstash removed
status NOT set on INSERT (DB default applies)
MUST be deployed with --no-verify-jwt
npm registry @agentcommunity/dmv-agent@0.2.2 published canonical package
dmv-agent@0.1.2 published compatibility alias
npm source @agentcommunity/dmv-agent@0.3.0 source-ready, not published
dmv-agent@0.1.3 source-ready, depends on canonical ^0.3.0
Lookup boundary GET /api/lookup + lookup-agent LIVE — deployed on main fabafe6 (PR #20), Worker
d9755e66-3883-4970-be84-a59307011f14 (2026-07-22),
direct Edge gate verified 403. Re-verified live
2026-08-01: real issued/not_found, not "unavailable".
DMV main latest as of this handoff see `git log` for the current HEAD
PAGE main shared Supabase project hardened independently via PR #82 (nembal/agentcommunity_page)
PAGE side is done; don't touch unless asked
Browser / CLI / MCP / JS API registration all converge on /api/register on the dmv-agentcommunity Cloudflare Worker. Browser path: validate JSON → require cf-turnstile-response → Turnstile siteverify (hostname + dmv_register action checked server-side) → shared CF rate limiters (RL_OTP_EMAIL 5/60s and RL_OTP_IP_EMAIL 4/60s, both sharing namespace_id at the Cloudflare account level with agentcommunity_PAGE) → forward to Supabase register-agent with DMV_PROXY_SECRET. Production CLI/MCP traffic currently uses the older DMV-local KV cooldown after the shared limits. This branch changes that step to one exact SQLite Durable Object budget per hashed fingerprint: claim before upstream, commit only a well-formed fresh mint, release only an audited pre-INSERT response or exact replay, leave every uncertain outcome pending, and fail closed. Supabase validates again, enforces the DB lifetime cap (5 unendorsed / 12 endorsed per email), generates the certificate ID, and INSERTs with certificate_id set while omitting status so the DB default applies. Its nonessential post-INSERT queue count cannot turn a committed mint into a 5xx; structured errors and rejected queries both yield a 201 with queue_number: null.
The live certificate-verification boundary follows the same model. Public
clients use only
GET https://dmv.agentcommunity.org/api/lookup?id=CERT-ID. The Worker validates
the check digit, applies coarse/eventually consistent RL_CERT_LOOKUP at 60/60,
then uses one CERT_LOOKUP_LIMITER SQLite Durable Object per hashed IP for exact
atomic 30/60 accounting and headers. It caches issued results for 300 seconds
and typed not-found results for 60 seconds in BADGE_CACHE_KV, and returns only certificate_id, status,
valid_format, issued, agent_name, and certificate_url. The lookup-agent
Edge Function change makes it an internal DMV_PROXY_SECRET-gated upstream with
exact typed HTTP 200 issued/not_found envelopes; non-200 or malformed
envelopes are unavailable and uncached. Direct calls and domain lookup are
unsupported.
issued: true means a matching registration row
exists, not that email verification, .agent allocation, or DNS delegation is done.
supabase functions deploy register-agent --project-ref tcymqfwwphacnosnnzxl --no-verify-jwtThe DMV worker does not forward an Authorization header (the REGISTER_FORWARD_REQUEST_HEADERS allow-list in worker/index.ts:934 only carries content-type, accept, accept-encoding, accept-language, user-agent, plus worker-set x-forwarded-* headers and x-dmv-proxy set to the DMV_PROXY_SECRET shared secret). If you deploy without --no-verify-jwt, Supabase's platform-level JWT verification layer fires first and the worker can't reach the function → every real registration gets 401 Missing authorization header. The x-dmv-proxy gate inside the function is the actual anti-bypass defense; Supabase's JWT layer would just break the worker forwarding without adding real security.
The PAGE registration_status enum has six values: pending_profile | pending_signature | complete | failed | blocked | anonymized. It has never had provisional_dmv. An earlier version of AUTH_DMV.md called for adding it via ALTER TYPE, but PAGE's team built a different design: PAGE identifies DMV rows by certificate_id IS NOT NULL (see agentCommunity_PAGE/supabase/migrations/20260211000000_dmv_schema_and_trigger_guard.sql — welcome/endorsement email triggers guard with IF NEW.certificate_id IS NOT NULL THEN RETURN NEW;).
Do NOT run ALTER TYPE registration_status ADD VALUE 'provisional_dmv'. It would add a value PAGE's TypeScript types, auth hub, and admin views don't know about, and it's unnecessary — certificate_id already does the job.
DMV's register-agent/index.ts INSERT omits the status field entirely. The DB default (DEFAULT 'pending_profile'::registration_status NOT NULL, baseline.sql:3345) applies. If a future change adds status: '...' back to the INSERT, the server will 500 with 22P02 invalid input value for enum. Been there.
wrangler secret put is blocked on the dmv-agentcommunity worker by a version-mismatch guard (see docs/plans/2026-04-08-handoff-prompt.md §69 for the operational workaround). Install/rotate the TURNSTILE_SECRET_KEY via the Cloudflare dashboard: Workers & Pages → dmv-agentcommunity → Settings → Variables and Secrets. Make sure you paste it as Secret type (encrypted), not Text (plaintext). Whitespace paste errors are a gotcha — if a registration fails with turnstile_failed, re-copy from the Turnstile widget page (Cloudflare dashboard → Turnstile → the widget with site key 0x4AAAAAAC2BwC5T9LSdndaK) and re-paste.
A push to DMV main triggers a Cloudflare worker redeploy (via CF git integration), but it does not trigger a Supabase functions deploy. If you change supabase/functions/register-agent/index.ts you have to run supabase functions deploy register-agent --project-ref tcymqfwwphacnosnnzxl --no-verify-jwt manually. The DMV repo has no GitHub Actions workflow for this.
RL_OTP_EMAIL namespace_id 4005 and RL_OTP_IP_EMAIL namespace_id 4007 are shared at the Cloudflare account level with agentCommunity_PAGE. A single attacker spending email-keyed quota on PAGE has less of it available on DMV. This is intentional. If PAGE ever bumps these values or changes the email-hash keying function, DMV silently drifts — watch for it in cross-repo coordination. PAGE's RL_AUTH (4001) and RL_OTP_IP (4006) are NOT shared; RL_OTP_IP (4006) was dropped entirely from PAGE's wrangler.jsonc in PR #82 and nothing references it anymore.
REGISTER_FINGERPRINT_LIMITER is one SQLite Durable Object per SHA-256
machine-fingerprint hash and is never shared with PAGE. It is live as of
2026-08-02. Production recovery must preserve v1 CardRenderer, v2
CertificateLookupRateLimiter, the v3 class/export/migration/binding, and all
corresponding configuration in every roll-forward. The old
REGISTER_COOLDOWN_KV binding is retained as unused compatibility state.
Use bunx @agentcommunity/dmv-agent register. The published canonical package
is @agentcommunity/dmv-agent@0.2.2; dmv-agent@0.1.2 is a compatibility alias,
not a second capability. This branch prepares canonical source 0.3.0 and
compatibility alias source 0.1.3 with a dependency on ^0.3.0, but neither
may be published without the separate
release-owner authorization and artifact gates. Publish only from the explicit
package directory, never the repo root. The root package.json has
"private": true as a safety rail, but a missing private flag would ship the
whole repository. See .gitignore for the .worktrees/ exclusion that
backstops this.
Set the same generated DMV_PROXY_SECRET on Cloudflare and Supabase without
writing it to source. Require Docker/container-build gates on a capable host,
confirm account-wide native namespace 1002, the lookup bindings, and v1/v2
migrations, then merge main. Cloudflare Git automatic deploy is authoritative;
manual deploy is a non-concurrent fallback only if auto deploy never starts.
Worker first is intentional. The completed rollout recorded invalid 400, then
deployed only lookup-agent --no-verify-jwt and proved REEF-068-BD0Q issued,
ZZZZ-FFF-FFFD not-found, direct secretless 403, exact call-31 429, minute
rollover, and health/card/badge/permalink/registration smokes. Never roll back
to pre-v3: preserve the deployed v1/v2/v3 migrations, DO exports, and bindings in
a roll-forward. If a
future Worker change fails, stop before Edge; if Edge fails after gating, keep
the safe Worker 503 and roll Edge forward without reopening direct access.
Update all status surfaces in DEPLOY.md after any future evidence-backed rollout.
- Preserve the live lookup boundary. Future changes must retain public certificate-ID-only lookup, exact 30/60 enforcement, the direct Edge 403 gate, and the no-data-mutation verification discipline.
-
PAGE test fixture fallout from
types/supabase.tsregen — branchchore/regen-supabase-typesonnembal/agentcommunity_pageis pushed but not merged because 58 test-file type errors surfaced when the generated types tightened. All errors are test fixtures out of sync with reality, not app code bugs. List is in the commit message off3cbd53. Fix and merge as a focused PAGE PR. -
PAGE has 2 unpushed commits on local main (as of session close 2026-04-09) that are the user's own work:
a621634 fix(auth): unswallow stale-intent redirect at /auth + repo trust passfd694f7 fix(profile): move social og images off api pathThese need a review pass and thengit push origin main. Not touched by the hardening session — they appeared in the PAGE working tree during the session but were not ours.
-
Orphan
user_domainsrows from the diagnostic loop — MIGHT exist for cert IDsNEON-219-A55AandFLUX-79E-D61O. Quick check:SELECT id, user_id, certificate_id, domain_name FROM public.user_domains WHERE certificate_id IN ('NEON-219-A55A', 'FLUX-79E-D61O');
If rows exist, record the result and request explicit user approval before any cleanup. Do not delete Supabase data as part of a diagnostic; escalate the proposed target set and recovery plan instead.
-
Historical
llms.txtlookup gap (closed 2026-07-22) — PR #8 removed a broken/api/lookupreference while no Worker route existed. The live route is now restored and documented. Do not restore direct Edge or domain-query examples. -
PAGE member count hygiene — DMV rows land with
status = 'pending_profile'(the DB default). If PAGE's admin stats or homepage counts treat allpending_profilerows as regular members, they'll inflate once DMV sees real traffic. Audit PAGE member count queries and decide whether to exclude unclaimed DMV rows (e.g.,WHERE certificate_id IS NULL OR (user_id IS NOT NULL AND auth_user_email_verified)).
- Don't run
ALTER TYPE registration_status ADD VALUE 'provisional_dmv'(see quirk #2) - Don't deploy
register-agentwithout--no-verify-jwt(see quirk #1) - Don't
wrangler secret putondmv-agentcommunity(see quirk #3) — use the dashboard - Don't publish either npm package manually. Use the owner-authorized
publish-dmv-packages.ymlworkflow, which publishes the exact verified canonical tarball first, blocks the alias until canonical provenance passes, and requires the protectednpm-productionenvironment onmain. - Don't push PAGE changes to main without reviewing them — PAGE auto-deploys via CF git integration
- Don't remove the
x-dmv-proxygate or weaken it back to a public constant — it's now secret-backed (DMV_PROXY_SECRET, constant-time compared, fail-closed) and is the only thing closing the direct-Supabase bypass - Don't call or document
lookup-agentas a public API, restore domain lookup, or deploy the lookup Edge Function before the Worker replacement
Key DMV commits from the hardening arc (2026-04-08/09, all on main):
8d73924 fix(supabase): drop stale provisional_dmv status from register-agent INSERT
c94a592 docs: purge stale provisional_dmv references across all surfaces
05b2075 chore(dmv-agent): bump to 0.2.0 for worker-proxied registration
9559e9e chore: ignore .worktrees/ in gitignore
570c30f Merge PR #8 (fix bypass + llms.txt lookup)
fa22190 Merge PR #7 (the main hardening PR)
Key files (read these before touching the registration flow):
worker/index.ts— worker entry,/api/registerhandler athandleRegister(), Turnstile verification atverifyTurnstileToken(), forward logic withx-dmv-proxyheader set at:1091worker/registration-fingerprint-rate-limiter.ts— transactional SQLite Durable Object that reserves, commits, releases, and conservatively recovers exact fingerprint-budget slots without storing raw fingerprints or IP addressesworker/register-fingerprint-cooldown.ts— Worker-side adapter that talks to the per-hash Durable Object for claim/completion, applies the closed upstream outcome classification, and keeps ambiguous results pendingsupabase/functions/register-agent/index.ts— Supabase upstream, thex-dmv-proxygate, no status field on INSERTpackages/dmv-agent/src/register.ts— CLI/MCP client, POSTs tohttps://dmv.agentcommunity.org/api/registerjs/supabase.js— browser client, same-origin/api/registerindex.html— Turnstile widget mount,<meta name="dmv-turnstile-site-key">carries the public site keywrangler.jsonc— all worker bindings (rate limiters, KV, R2, Durable Object)public/_headers— CSP for static assets (also mirrored inPERMALINK_CSPconstant inworker/index.tsfor/c/*routes — keep them in sync manually)docs/plans/2026-04-08-cross-repo-hardening-handoff-prompt.md— design spec, non-negotiable ordering rules (CAPTCHA before counters, etc.)
PAGE files to be aware of (read-only from DMV's perspective):
agentCommunity_PAGE/supabase/migrations/20260210999999_dmv_add_agent_enum.sqlagentCommunity_PAGE/supabase/migrations/20260211000000_dmv_schema_and_trigger_guard.sqlagentCommunity_PAGE/supabase/migrations/20260211000100_dmv_registration_trigger.sqlagentCommunity_PAGE/supabase/migrations/20260203000000_baseline.sqlline 3308 for theregistrationstable definitionagentCommunity_PAGE/wrangler.jsoncfor PAGE's own rate-limit namespace bindings (shared with DMV)
Tail the worker while debugging:
pnpm cf:tail
# or
pnpm wrangler tailSmoke test the direct-access gate:
curl -X POST https://tcymqfwwphacnosnnzxl.supabase.co/functions/v1/register-agent \
-H 'Content-Type: application/json' -d '{}'
# Expect: 403 direct_access_deprecated
curl -X POST https://tcymqfwwphacnosnnzxl.supabase.co/functions/v1/register-agent \
-H 'Content-Type: application/json' -H 'x-dmv-proxy: v1' -d '{}'
# Expect: 403 direct_access_deprecated
# The live gate value is now the DMV_PROXY_SECRET shared secret (set on both the
# Cloudflare Worker and the Supabase project). The public `v1` constant was retired
# 2026-05-29, so this curl can no longer reach validation — only the worker, which
# forwards the real secret, gets through.Smoke test the worker proxy:
curl -X POST https://dmv.agentcommunity.org/api/register \
-H 'Content-Type: application/json' \
-d '{"agent_name":"x","email":"x@y.com","signup_source":"ui"}'
# Expect: 400 turnstile_required (if agent_name ≥ 3 chars)
# or 400 agent_name must be at least 3 characters (at validation stage)Manual Worker fallback only if the Cloudflare Git build did not trigger:
First prove in the Cloudflare dashboard that an automatic build/deployment is neither active nor already started. Then use the canonical fallback exactly once; never run it concurrently with an automatic build:
pnpm cf:deployFor lookup changes, deploy this Worker step first. Then deploy the internal upstream:
supabase functions deploy lookup-agent --project-ref tcymqfwwphacnosnnzxl --no-verify-jwtPublic lookup smoke:
curl "https://dmv.agentcommunity.org/api/lookup?id=MESA-DD6-660J"
# Expect: a six-field issued/not_found result plus RateLimit-* headers.Re-deploy the edge function (after code change to supabase/functions/register-agent/index.ts):
supabase functions deploy register-agent --project-ref tcymqfwwphacnosnnzxl --no-verify-jwtThe --no-verify-jwt is not optional. See quirk #1.
This was a long arc: started with "add rate limiting and Turnstile to DMV registration" and ended with a cross-repo schema design discovery that revealed a latent bug in the DMV edge function that had been there since the Upstash days but never triggered because the function had never actually been exercised end-to-end in production. The hardening itself was straightforward; the debugging loop that followed the first production deploy was the interesting part. Read AUTH_DMV.md "Status Lifecycle" section for the full story.
If you pick this up and something is broken, the single highest-leverage thing is: tail the worker (pnpm cf:tail), reproduce the failure, and look at the analytics events in the dmv_worker_events Analytics Engine dataset. The worker emits events with category: 'register' and a tier blob that tells you exactly which gate a failing request hit. Actual tier vocabulary (from worker/index.ts emitRegisterAnalytics call sites):
'405'— non-POST method'validation'— JSON shape / field validation failed'invalid_json'— request body wasn't parseable JSON'turnstile_required'— browser path missingcf-turnstile-response'turnstile_failed'— Turnstile siteverify rejected the token'machine_fingerprint_required'— CLI/MCP path missingmachine_fingerprint'rate_limited'— shared CF limiter (RL_OTP_EMAILorRL_OTP_IP_EMAIL) fired'fingerprint_cooldown'— exact DMV-local fingerprint mint budget fired'supabase'— request successfully forwarded and Supabase returned 2xx'supabase_<status>'— request forwarded but Supabase returned a non-2xx (e.g.,supabase_403for lifetime-cap hits,supabase_400for Supabase validation,supabase_500for DB errors)
So a query like tier = 'supabase_500' pinpoints upstream DB failures, tier LIKE 'supabase_%' shows all upstream errors, and tier NOT LIKE 'supabase%' shows everything the worker rejected before it ever hit Supabase.
Good luck.