Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 4x 4x 42x 9x 33x 33x 31x 16x 15x 2x 2x 29x 29x 8x 29x | /**
* Runtime configuration — operator switches that take effect WITHOUT a deploy.
*
* WHY THIS FILE EXISTS
* --------------------
* Auto-publish (see `publish-readiness.ts`) moves a studio from `draft` to
* `published` on a public marketplace with no human in the loop. A bad listing
* reaching the marketplace is irreversible in reputation terms, so the feature
* needs a brake that does not require a code change, a CI run and a deploy.
*
* WHAT MECHANISM THIS USES, AND WHY
* ---------------------------------
* This codebase has no settings/feature-flag table and no dedicated config KV
* namespace. The one runtime store that is bound in every environment and is
* writable from outside a deploy is the `KV_CACHE` KV namespace
* (`apps/api/wrangler.jsonc` → `kv_namespaces`). So config lives there under a
* reserved `config:` key prefix.
*
* That prefix is safe to share with the cache:
* - `createDualCache()` callers only ever write `pros:*`, `taxonomy:*`,
* `marketplace:*`, `audience:*` … keys — never `config:*`.
* - `POST /api/admin/cache/purge` purges an explicit key list (`PURGE_KEYS`
* in `routes/admin/cache.routes.ts`), not the whole namespace, and is
* blocked in production.
* - Reads here bypass the dual cache entirely, so a flip takes effect on the
* next request rather than after an L1 TTL. No stale kill switch.
*
* HOW AN OPERATOR FLIPS IT (no deploy, takes effect immediately)
* -------------------------------------------------------------
* # stop all auto-publishing (production)
* bunx wrangler kv key put --remote \
* --namespace-id=<prod KV_CACHE id from apps/api/wrangler.jsonc> \
* "config:auto_publish_pro" "off"
*
* # turn it back on
* bunx wrangler kv key delete --remote \
* --namespace-id=<prod KV_CACHE id> "config:auto_publish_pro"
*
* FAILURE MODE
* ------------
* Absent key → enabled (nobody has flipped it; the feature is on as designed).
* Read error / missing binding → DISABLED. A missed auto-publish is recoverable
* by an admin publish; a bad public listing is not. Fail towards the
* recoverable outcome.
*/
import { logger } from "./logger";
/** Reserved KV keys. Namespaced with `config:` so nothing else collides. */
export const RUNTIME_CONFIG_KEYS = {
/** Set to any OFF value to suppress draft → published auto-publishing. */
AUTO_PUBLISH_PRO: "config:auto_publish_pro",
} as const;
/**
* Values that mean "off". Anything else (including the empty-ish `"on"`,
* `"true"`, or a stray value an operator typed) leaves the feature enabled, so
* a typo can never silently *enable* something that was meant to be off —
* only the explicit words below disable, and they are matched generously.
*/
const OFF_VALUES = new Set(["off", "false", "0", "no", "disabled", "disable"]);
/** The slice of the Worker env this module needs. */
export type RuntimeConfigEnv = {
KV_CACHE?: KVNamespace;
};
/** Normalised result of a flag read, for logging and tests. */
export type FlagRead = {
enabled: boolean;
/** Where the answer came from — surfaced in logs so an operator can
* confirm the brake was actually read and not defaulted. */
source: "kv" | "default" | "unavailable" | "error";
raw: string | null;
};
/**
* Read a boolean runtime flag that defaults to ON.
*
* Exported for the one caller below and for any future no-deploy switch; the
* key must come from `RUNTIME_CONFIG_KEYS` so every flag is discoverable here.
*/
export async function readDefaultOnFlag(
env: RuntimeConfigEnv,
key: (typeof RUNTIME_CONFIG_KEYS)[keyof typeof RUNTIME_CONFIG_KEYS],
): Promise<FlagRead> {
if (!env.KV_CACHE) {
return { enabled: false, source: "unavailable", raw: null };
}
try {
const raw = await env.KV_CACHE.get(key);
if (raw === null || raw === undefined) {
return { enabled: true, source: "default", raw: null };
}
return {
enabled: !OFF_VALUES.has(raw.trim().toLowerCase()),
source: "kv",
raw,
};
} catch (err) {
logger.error("runtime_config_read_failed", {
event: "runtime_config_read_failed",
key,
error: err instanceof Error ? err.message : String(err),
});
return { enabled: false, source: "error", raw: null };
}
}
/**
* Is draft → published auto-publishing allowed right now?
*
* This is the ONLY brake that works without a deploy. The other brake is the
* existing admin unpublish (`POST /api/admin/pros/:id/disable`), which is
* unaffected by this flag.
*/
export async function isAutoPublishEnabled(
env: RuntimeConfigEnv,
): Promise<boolean> {
const read = await readDefaultOnFlag(
env,
RUNTIME_CONFIG_KEYS.AUTO_PUBLISH_PRO,
);
if (!read.enabled && read.source === "unavailable") {
logger.warn("auto_publish_disabled_no_kv_binding", {
event: "auto_publish_disabled_no_kv_binding",
key: RUNTIME_CONFIG_KEYS.AUTO_PUBLISH_PRO,
});
}
return read.enabled;
}
|