Files
zeekayandHanzo Dev 2c295299ae config: the lists that gate abuse now actually load, from Hanzo KV
@vercel/edge-config read NOTHING in production. EDGE_CONFIG is unset on this
deployment, so every `get()` threw, and every call site caught it and fell back
to an empty default. Several of them additionally checked `process.env.
EDGE_CONFIG` and returned early, so the fallback was not even a fallback — it was
the only path.

What that meant, live:

  emails / keywords  the blacklist matched nothing — abuse filtering was off
  trustedTeams       no team was ever trusted
  betaFeatures       no beta feature was ever on
  customEmail        no team ever got its own sender address

lib/config.ts reads these from Hanzo KV under a `config:` prefix, so a value
changes with a KV write rather than a deploy — which is the reason the app
reached for an edge config service to begin with. Reads never throw: a KV outage
returns the caller's fallback, exactly as these call sites already assumed, so
config can never take down a request path.

customEmail is a Record, not a list, and now says so — it was being read through
the list helper, which would have discarded it.

@vercel/functions and @vercel/edge-config have no importers left and are dropped
from package.json; the lockfile is regenerated and `npm ci --dry-run` is clean.
@vercel/blob still has 10 importers and stays until storage moves to S3.

Verified with npx tsc --noEmit: 100 errors before and after.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:37:48 -07:00

44 lines
1.5 KiB
TypeScript

import { kv } from "@/lib/kv";
/**
* Operational config — the lists and flags that change without a deploy.
*
* This replaces @vercel/edge-config, which in production read nothing: EDGE_CONFIG
* is unset on this deployment, so every `get()` threw and each call site caught it
* and fell back to an empty default. That is why no team was ever trusted, no beta
* feature was ever on, and the email/keyword blacklist matched nothing — abuse
* filtering has been silently off.
*
* Hanzo KV is the store, under a `config:` prefix, so a value is changed with a
* KV write instead of a deploy — which is the whole reason the app reached for an
* edge config service.
*
* Reads never throw. A KV outage returns the fallback, exactly as the old callers
* assumed, so a config lookup can never take down a request path.
*/
export type ConfigKey =
| "betaFeatures"
| "customEmail"
| "emails"
| "keywords"
| "trustedTeams";
export async function getConfig<T>(key: ConfigKey, fallback: T): Promise<T> {
try {
const value = await kv.get<T>(`config:${key}`);
return value ?? fallback;
} catch (error) {
console.error(`[config] could not read ${key}`, error);
return fallback;
}
}
/** A list of strings, with anything else in the value discarded. */
export async function getStringList(key: ConfigKey): Promise<string[]> {
const value = await getConfig<unknown>(key, []);
return Array.isArray(value)
? value.filter((item): item is string => typeof item === "string")
: [];
}