feat(buyer): PDF_ENABLED kill switch for the PDF summarizer
PDF_ENABLED=0 skips the JobStore/PdfJobDb wiring in runMain, so every /v1/pdf-* route responds 503 (the pre-existing disabled path). Default stays enabled; only "0"/"1" accepted, misconfig fails boot loudly.
This commit is contained in:
@@ -42,6 +42,8 @@ NETWORK_ID=0
|
||||
# Upload a PDF → chunk it → pay suppliers to summarize each chunk (one escrow
|
||||
# per map + reduce call) → full-book summary. All knobs are optional; defaults
|
||||
# shown. Tune the caps to bound spend/abuse on mainnet.
|
||||
# Kill switch: "0" disables the feature entirely (every /v1/pdf-* route 503s):
|
||||
# PDF_ENABLED=1
|
||||
#
|
||||
# Hard caps (a job is refused if it would exceed these):
|
||||
# PDF_MAX_PAGES=400 # reject books larger than this
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
* API key" page knows where to POST /signup. When
|
||||
* unset, the SPA falls back to deriving "api." +
|
||||
* current host.
|
||||
* PDF_ENABLED — "1" (default) serves the PDF book summarizer
|
||||
* (/v1/pdf-* routes). Set "0" to disable the feature
|
||||
* entirely: no PDF job store is opened and every
|
||||
* /v1/pdf-* route responds 503.
|
||||
*/
|
||||
|
||||
const HEX64_RE = /^[0-9a-fA-F]{64}$/;
|
||||
@@ -62,6 +66,7 @@ export interface BuyerConfig {
|
||||
password: string;
|
||||
sessionSecret: string;
|
||||
cookieSecure: boolean;
|
||||
pdfEnabled: boolean;
|
||||
gatewayPublicUrl: string;
|
||||
}
|
||||
|
||||
@@ -148,6 +153,18 @@ export function loadConfig(env: Record<string, string | undefined>): BuyerConfig
|
||||
cookieSecure = cookieSecureStr === "1";
|
||||
}
|
||||
|
||||
// PDF_ENABLED: defaults to true. Set "0" to disable the PDF book summarizer
|
||||
// entirely — runMain skips the JobStore wiring and every /v1/pdf-* route
|
||||
// responds 503. Accept only "0" / "1" to keep boot-time misconfig loud.
|
||||
let pdfEnabled = true;
|
||||
const pdfEnabledStr = env.PDF_ENABLED;
|
||||
if (pdfEnabledStr !== undefined && pdfEnabledStr !== "") {
|
||||
if (pdfEnabledStr !== "0" && pdfEnabledStr !== "1") {
|
||||
throw new Error('loadConfig: PDF_ENABLED must be "0" or "1"');
|
||||
}
|
||||
pdfEnabled = pdfEnabledStr === "1";
|
||||
}
|
||||
|
||||
// GATEWAY_PUBLIC_URL — optional. Public base URL of the gateway, surfaced to
|
||||
// the SPA via the boot block (no trailing slash). Empty string means the SPA
|
||||
// derives the URL from its own host ("api." + location.host).
|
||||
@@ -167,6 +184,7 @@ export function loadConfig(env: Record<string, string | undefined>): BuyerConfig
|
||||
password,
|
||||
sessionSecret,
|
||||
cookieSecure,
|
||||
pdfEnabled,
|
||||
gatewayPublicUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,7 +96,11 @@ export async function runMain(env: Record<string, string | undefined>): Promise<
|
||||
|
||||
// PDF book summarizer: one job registry, sharing the live marketplace SDK,
|
||||
// chain, wallet, and (optional) archive. Caps come from PDF_* env vars.
|
||||
// PDF_ENABLED=0 skips the whole wiring; createApp then registers every
|
||||
// /v1/pdf-* route as a 503 stub (feature disabled).
|
||||
const pdfCaps = loadPdfCaps(env);
|
||||
let jobStore: JobStore | undefined;
|
||||
if (config.pdfEnabled) {
|
||||
// Durable job store (SQLite under ARCHIVE_DIR) so past summaries survive
|
||||
// restarts and navigation. Best-effort: if it can't open, jobs fall back to
|
||||
// in-memory only (lost on restart) but the feature still works.
|
||||
@@ -110,7 +114,7 @@ export async function runMain(env: Record<string, string | undefined>): Promise<
|
||||
err instanceof Error ? err.message : String(err),
|
||||
);
|
||||
}
|
||||
const jobStore = new JobStore({
|
||||
jobStore = new JobStore({
|
||||
marketplace,
|
||||
chain,
|
||||
walletKey,
|
||||
@@ -119,6 +123,9 @@ export async function runMain(env: Record<string, string | undefined>): Promise<
|
||||
caps: pdfCaps,
|
||||
db: pdfJobDb,
|
||||
});
|
||||
} else {
|
||||
console.log("[buyer] pdf summarizer disabled (PDF_ENABLED=0)");
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
distPath,
|
||||
|
||||
@@ -40,7 +40,7 @@ export function registerPdfRoutes(
|
||||
res,
|
||||
503,
|
||||
"service_unavailable",
|
||||
"PDF summarizer disabled (buyer booted without chain/marketplace/archive deps)",
|
||||
"PDF summarizer disabled",
|
||||
);
|
||||
app.post("/v1/pdf-upload", disabled);
|
||||
app.all(/^\/v1\/pdf-jobs(\/.*)?$/, disabled);
|
||||
|
||||
@@ -87,6 +87,19 @@ describe("loadConfig(env)", () => {
|
||||
const env = { ...VALID_ENV, COOKIE_SECURE: "true" };
|
||||
expect(() => loadConfig(env)).toThrow(/COOKIE_SECURE/);
|
||||
});
|
||||
|
||||
it("defaults pdfEnabled to true when PDF_ENABLED is not provided", () => {
|
||||
expect(loadConfig(VALID_ENV).pdfEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('honours PDF_ENABLED="0" (summarizer kill switch)', () => {
|
||||
const config = loadConfig({ ...VALID_ENV, PDF_ENABLED: "0" });
|
||||
expect(config.pdfEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it("throws when PDF_ENABLED is not 0 or 1", () => {
|
||||
expect(() => loadConfig({ ...VALID_ENV, PDF_ENABLED: "false" })).toThrow(/PDF_ENABLED/);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── createApp tests ──────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user