Spaces:
Sleeping
Sleeping
Download scripts/predeploy-smoke.ts from Leon4gr45/nodepad-hypotheses: direct link, hf CLI and curl.
- Browser
- Download file 15.7 kB
-
https://huggingface.co/spaces/Leon4gr45/nodepad-hypotheses/resolve/main/scripts/predeploy-smoke.ts
- Command line
-
hf download hf://spaces/Leon4gr45/nodepad-hypotheses/scripts/predeploy-smoke.ts
-
curl -L -o predeploy-smoke.ts https://huggingface.co/spaces/Leon4gr45/nodepad-hypotheses/resolve/main/scripts/predeploy-smoke.ts
15.7 kB
| import assert from "node:assert/strict" | |
| import { spawn, type ChildProcess } from "node:child_process" | |
| import { createServer, type Server } from "node:http" | |
| import { cp, mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises" | |
| import path from "node:path" | |
| import { tmpdir } from "node:os" | |
| import { fileURLToPath } from "node:url" | |
| export const FIXTURES = { NODEPAD_LOGIN_KEY: "SUPER_SECRET_LOGIN_928374", NODEPAD_API_KEY: "SUPER_SECRET_NODEPAD_API_928374", OPENAI_API: "SUPER_SECRET_OPENAI_928374" } | |
| export function isolatedEnv(): NodeJS.ProcessEnv { | |
| const env = { ...process.env } | |
| for (const name of Object.keys(env)) if (/^(NODEPAD_|OPENAI_|GH_TOKEN$|GITHUB_TOKEN$|HF_TOKEN$|NEXT_PUBLIC_.*(?:KEY|TOKEN|SECRET))/.test(name)) delete env[name] | |
| return { ...env, ...FIXTURES, NODE_ENV: "production", NODEPAD_GITHUB_SYNC: "false", NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT: "0", NODEPAD_RESEARCH_ENABLED: "false", NODEPAD_SYNTHESIS_MODE: "deterministic", NEXT_TELEMETRY_DISABLED: "1" } | |
| } | |
| const secretSafe = (value: string) => Object.values(FIXTURES).every(secret => !value.includes(secret)) | |
| const listen = (server: Server, host = "127.0.0.1") => new Promise<number>((resolve, reject) => { server.once("error", reject); server.listen(0, host, () => resolve((server.address() as { port: number }).port)) }) | |
| const close = (server: Server) => new Promise<void>(resolve => { server.closeAllConnections(); server.close(() => resolve()) }) | |
| export async function freePort() { const server = createServer(); const port = await listen(server); await close(server); return port } | |
| export async function smoke(docker = false) { | |
| const root = process.cwd() | |
| const temporary = await mkdtemp(path.join(tmpdir(), "nodepad-predeploy-")) | |
| let child: ChildProcess | undefined | |
| let logs = "" | |
| let modelVersion = 1 | |
| let discoveryFailure = false | |
| let completionFailure = false | |
| const observed: { model: string; authorized: boolean }[] = [] | |
| const groups: Record<string, { passed: number; failed: number; result: string }> = {} | |
| let group = "" | |
| const check = (condition: unknown, message: string) => { assert.ok(condition, message); groups[group].passed++ } | |
| async function section(name: string, action: () => Promise<void>) { | |
| group = name; groups[name] = { passed: 0, failed: 0, result: "PASS" } | |
| try { await action(); console.log(`PASS ${name}: ${groups[name].passed} assertions`) } | |
| catch (error) { groups[name].failed++; groups[name].result = "FAIL"; throw error } | |
| } | |
| const provider = createServer(async (req, res) => { | |
| const authorized = req.headers.authorization === `Bearer ${FIXTURES.OPENAI_API}` | |
| if (!authorized) { res.writeHead(401).end(); return } | |
| if (req.url === "/v1/models") { | |
| res.setHeader("Content-Type", "application/json") | |
| if (discoveryFailure) { res.writeHead(404).end("unsupported"); return } | |
| res.end(JSON.stringify({ data: ["model-a", "Spark-X2.5", "Qwen/Qwen3.8-27B-FP8", "strange/custom:model@v2", ...(modelVersion > 1 ? ["model-c"] : [])].map(id => ({ id })) })) | |
| return | |
| } | |
| if (req.url === "/v1/chat/completions") { | |
| let text = ""; for await (const chunk of req) text += chunk | |
| const body = JSON.parse(text); observed.push({ model: body.model, authorized }) | |
| if (completionFailure) { res.writeHead(503).end("fixture provider unavailable"); return } | |
| res.setHeader("Content-Type", "application/json") | |
| res.end(JSON.stringify({ model: body.model, choices: [{ message: { content: "NODEPAD_LLM_OK" } }] })) | |
| return | |
| } | |
| res.writeHead(404).end() | |
| }) | |
| let base = "" | |
| let cookie = "" | |
| const captures: string[] = [] | |
| async function request(url: string, method = "GET", body?: unknown, auth: "none" | "session" | "machine" | "bad-session" | "bad-machine" | "human-bearer" = "none", extra: Record<string, string> = {}) { | |
| const headers: Record<string, string> = { ...extra } | |
| if (auth === "session") headers.Cookie = cookie | |
| if (auth === "machine") headers.Authorization = `Bearer ${FIXTURES.NODEPAD_API_KEY}` | |
| if (auth === "bad-machine") headers.Authorization = "Bearer invalid-fixture" | |
| if (auth === "human-bearer") headers.Authorization = `Bearer ${FIXTURES.NODEPAD_LOGIN_KEY}` | |
| if (auth === "bad-session") headers.Cookie = "nodepad_session=invalid-fixture" | |
| if (body !== undefined) headers["Content-Type"] = "application/json" | |
| const response = await fetch(base + url, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), redirect: "manual", signal: AbortSignal.timeout(20_000) }) | |
| const text = await response.text(); captures.push(text) | |
| let data: any; try { data = JSON.parse(text) } catch { data = text } | |
| return { response, data, text } | |
| } | |
| async function stop() { | |
| if (!child || child.exitCode !== null || child.signalCode !== null) return | |
| const exited = new Promise<void>(resolve => child!.once("exit", () => resolve())) | |
| child.kill("SIGTERM") | |
| const timer = setTimeout(() => child?.kill("SIGKILL"), 5000) | |
| await exited; clearTimeout(timer) | |
| } | |
| try { | |
| const providerPort = await listen(provider, docker ? "0.0.0.0" : "127.0.0.1") | |
| const port = await freePort() | |
| base = `http://127.0.0.1:${port}` | |
| const env = { ...isolatedEnv(), NODEPAD_AUTH_DIR: path.join(temporary, "sessions"), NODEPAD_DATA_DIR: path.join(temporary, "data"), OPENAI_URL: `http://127.0.0.1:${providerPort}/v1`, OPENAI_MODEL: "qwythos-9b", PORT: String(port), HOSTNAME: "127.0.0.1" } | |
| await cp(path.join(root, ".next/static"), path.join(root, ".next/standalone/.next/static"), { recursive: true }) | |
| await cp(path.join(root, "public"), path.join(root, ".next/standalone/public"), { recursive: true }) | |
| const start = () => { | |
| child = spawn(process.execPath, [path.join(root, ".next/standalone/server.js")], { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] }) | |
| child.stdout?.on("data", data => { logs += data.toString() }); child.stderr?.on("data", data => { logs += data.toString() }) | |
| } | |
| const ready = async () => { | |
| for (let attempt = 0; attempt < 100; attempt++) { | |
| if (child?.exitCode !== null) throw new Error("Production server exited before readiness") | |
| try { if ((await fetch(base + "/health", { signal: AbortSignal.timeout(500) })).ok) return } catch { /* poll readiness */ } | |
| await new Promise(resolve => setTimeout(resolve, 100)) | |
| } | |
| throw new Error("Production server did not become ready") | |
| } | |
| start(); await ready() | |
| await section("Authentication", async () => { | |
| check((await request("/health")).response.status === 200, "health public") | |
| check((await request("/login")).response.status === 200, "login public") | |
| check((await request("/api-docs")).response.status === 200, "docs public") | |
| const anonymous = await request("/") | |
| check(anonymous.response.status === 307 && new URL(anonymous.response.headers.get("location")!, base).pathname === "/login", "anonymous redirect") | |
| check((await request("/api/auth/status")).data.authenticated === false, "anonymous status") | |
| for (const body of [{}, { accessKey: "" }, { accessKey: "wrong" }, { accessKey: FIXTURES.NODEPAD_API_KEY }]) check((await request("/api/auth/login", "POST", body)).response.status === 401, "bad human login") | |
| const login = await request("/api/auth/login", "POST", { accessKey: FIXTURES.NODEPAD_LOGIN_KEY }) | |
| check(login.response.status === 200 && login.data.success, "human login works") | |
| const header = login.response.headers.get("set-cookie") || "" | |
| for (const attribute of ["HttpOnly", "Secure", "Path=/", "SameSite=lax", "Max-Age="]) check(header.includes(attribute), "cookie security attribute") | |
| cookie = header.split(";")[0] | |
| check((await request("/", "GET", undefined, "session")).response.status === 200, "authenticated page no redirect loop") | |
| check((await request("/api/auth/status", "GET", undefined, "session")).data.authenticated === true, "session status") | |
| }) | |
| await section("Route protection", async () => { | |
| const routes: [string, string, unknown?][] = [ | |
| ["/api/v1/workspaces", "GET"], ["/api/v1/portfolio", "GET"], ["/api/v1/llm/status", "GET"], ["/api/v1/llm/models", "GET"], ["/api/v1/embeddings/status", "GET"], ["/api/v1/workspaces/matrix/adjudications", "GET"], | |
| ...["context", "graph", "evidence", "hypotheses", "brain", "events"].map(name => [`/api/v1/workspaces/matrix/${name}`, "GET"] as [string, string]), | |
| ["/api/v1/workspaces/matrix/evidence", "POST", { claim: "Matrix evidence" }], ["/api/v1/workspaces/matrix/heartbeat", "POST", { runId: "matrix" }], ["/api/v1/workspaces/matrix/brain/export", "POST", {}], | |
| ] | |
| for (const [url, method, body] of routes) for (const auth of ["none", "bad-session", "bad-machine", "human-bearer", "session", "machine"] as const) { | |
| const result = await request(url, method, body, auth) | |
| check(auth === "session" || auth === "machine" ? result.response.ok : result.response.status === 401, `protected matrix ${method} ${url} ${auth}`) | |
| } | |
| }) | |
| await section("Custom models", async () => { | |
| const models = await request("/api/v1/llm/models", "GET", undefined, "session") | |
| check(models.data.defaultModel === "qwythos-9b", "environment default") | |
| for (const id of ["qwythos-9b", "Spark-X2.5", "Qwen/Qwen3.8-27B-FP8", "strange/custom:model@v2"]) check(models.data.models.some((m: any) => m.id === id), "opaque discovered model") | |
| modelVersion++ | |
| check((await request("/api/v1/llm/models", "GET", undefined, "session")).data.models.some((m: any) => m.id === "model-c"), "refresh exposes new model") | |
| discoveryFailure = true | |
| check((await request("/api/v1/llm/models", "GET", undefined, "session")).data.models.some((m: any) => m.id === "qwythos-9b"), "failed discovery retains default") | |
| discoveryFailure = false | |
| check((await request("/api/v1/llm/test", "POST", {}, "session")).data.content === "NODEPAD_LLM_OK", "mock inference success") | |
| check(observed.at(-1)?.model === "qwythos-9b" && observed.at(-1)?.authorized, "default inference payload") | |
| check((await request("/api/v1/llm/chat/completions", "POST", { model: "my-private-model-v37", messages: [{ role: "user", content: "test" }] }, "session")).response.ok, "manual model accepted") | |
| check(observed.at(-1)?.model === "my-private-model-v37", "manual inference payload") | |
| }) | |
| await section("Nodepad core", async () => { | |
| const evidence = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "Independent demand", strength: 5, sourceType: "interview" }, "machine", { "Idempotency-Key": "e1", "x-nodepad-actor-type": "agent", "x-nodepad-actor-id": "fixture-agent" }) | |
| check(evidence.response.status === 201 && evidence.data.workspaceId === "workspace-a" && evidence.data.provenance.actorId === "fixture-agent", "evidence persisted actor metadata") | |
| const duplicate = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "Independent demand" }, "machine", { "Idempotency-Key": "e1" }) | |
| check(duplicate.data.id === evidence.data.id, "idempotent evidence") | |
| const hypothesis = await request("/api/v1/workspaces/workspace-a/hypotheses", "POST", { statement: "Demand exists", supportingEvidence: [evidence.data.id], evidenceScore: 100, confidence: 100, qualification: "strongly_qualified" }, "machine") | |
| check(hypothesis.response.status === 201 && hypothesis.data.confidence < 100, "scoring overrides supplied LLM authority") | |
| for (const name of ["context", "graph", "evidence", "hypotheses", "brain"]) check((await request(`/api/v1/workspaces/workspace-a/${name}`, "GET", undefined, "machine")).response.ok, "workspace retrieval") | |
| check((await request("/api/v1/workspaces/workspace-b/evidence", "GET", undefined, "machine")).data.length === 0, "workspace isolation") | |
| const context = (await request("/api/v1/workspaces/workspace-a/context", "GET", undefined, "machine")).data | |
| check((await request("/api/v1/workspaces/workspace-a/research/tasks", "POST", { question: "q", objective: "support" }, "machine", { "If-Match": String(context.revision) })).response.ok, "revision write") | |
| const conflict = await request("/api/v1/workspaces/workspace-a/research/tasks", "POST", { question: "stale", objective: "support" }, "machine", { "If-Match": String(context.revision) }) | |
| check(conflict.response.status === 409 && conflict.data.error.startsWith("revision_conflict"), "stale revision conflict") | |
| completionFailure = true | |
| check((await request("/api/v1/workspaces/workspace-a/syntheses/run", "POST", { synthesisType: "S011", runId: "fallback" }, "machine")).response.ok, "deterministic synthesis without LLM") | |
| const before = observed.length | |
| for (let i = 0; i < 3; i++) check((await request("/api/v1/workspaces/workspace-a/heartbeat", "POST", { runId: `repeat-${i}` }, "machine")).response.ok, "deterministic heartbeat") | |
| check(observed.length === before, "heartbeat makes no inference calls") | |
| const graph = (await request("/api/v1/workspaces/workspace-a/graph", "GET", undefined, "machine")).data | |
| check(new Set(graph.nodes.map((n: any) => n.id)).size === graph.nodes.length && graph.nodes.length < 200, "bounded deduplicated graph") | |
| const oversized = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "x".repeat(1_000_001) }, "machine") | |
| check(!oversized.response.ok && oversized.data.error === "request_body_too_large", "oversized mutation rejected") | |
| completionFailure = false | |
| }) | |
| await section("Secret isolation", async () => { | |
| for (const response of captures) check(secretSafe(response), "response contains no fixture credentials") | |
| check(secretSafe(logs), "production logs secret safe") | |
| async function scan(directory: string) { | |
| for (const entry of await readdir(directory, { withFileTypes: true })) { | |
| const file = path.join(directory, entry.name) | |
| if (entry.isDirectory()) await scan(file) | |
| else check(secretSafe(await readFile(file, "utf8")), "client/persistent file secret safe") | |
| } | |
| } | |
| await scan(path.join(root, ".next/static")) | |
| await scan(temporary) | |
| }) | |
| await section("Persistence and logout", async () => { | |
| await stop(); start(); await ready() | |
| check((await request("/api/auth/status", "GET", undefined, "session")).data.authenticated === true, "session survives process restart") | |
| check((await request("/api/v1/workspaces/workspace-a/evidence", "GET", undefined, "machine")).data.length === 1, "evidence survives process restart") | |
| check((await request("/api/auth/logout", "POST", undefined, "session")).response.ok, "logout success") | |
| check((await request("/api/v1/workspaces", "GET", undefined, "session")).response.status === 401, "old cookie revoked") | |
| check((await request("/api/v1/workspaces", "GET", undefined, "machine")).response.ok, "machine independent of logout") | |
| }) | |
| return groups | |
| } catch (error) { | |
| const message = Object.values(FIXTURES).reduce((text, secret) => text.split(secret).join("[FIXTURE_REDACTED]"), error instanceof Error ? error.message : "smoke failure") | |
| throw Object.assign(new Error(`${group || "Startup"}: ${message}`), { groups }) | |
| } finally { | |
| await stop(); await close(provider); await rm(temporary, { recursive: true, force: true }) | |
| } | |
| } | |
| if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) { | |
| smoke().catch(error => { console.error(`FAIL ${error instanceof Error ? error.message : "local production smoke"}`); process.exitCode = 1 }) | |
| } | |