UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

394 lines (393 loc) • 15 kB
/** * Credentials a lender provisioned onto this device, and the leases that keep * them legitimate. * * This is the borrower's half of **complete** sharing. The credential itself * lives in the normal token store and is routed like any other account — that is * the point, since it survives the lender being offline. What lives here is the * proof that the lender still consents, and the machinery to keep it fresh. * * **The enforcement is cooperative and the code should say so.** A resident * credential sits on a machine its holder controls, and the token store is * obfuscated rather than encrypted. Someone determined to bypass this can. What * these checks buy is that the honest path is also the correct one: a borrower * running the shipped software stops when the lender says stop, and stops on its * own if the lender becomes unreachable for longer than the lease allows. * * @module proxy/residentGrants */ import { readFile, stat } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; import { AsyncMutex } from "../utils/asyncMutex.js"; import { logger } from "../utils/logger.js"; import { evaluateLease, isHeartbeatDue, isLeaseAuthentic, } from "./shareLease.js"; import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js"; const RESIDENT_FILE = "proxy-resident-grants.json"; const HEARTBEAT_TIMEOUT_MS = 10_000; /** * How long a read may trust the cache before it stats the file again. * * The CLI writes this file from a separate process — `share accept`, `peer * provision`, a manual removal — so a load-once cache would let a long-lived * proxy keep serving from a grant the operator revoked minutes ago. */ const RELOAD_TTL_MS = 1_000; let customFilePath = null; let cache = {}; let cacheLoadedAt = 0; let cacheMtimeMs = -1; let cacheValid = false; const mutationMutex = new AsyncMutex(); export function initResidentGrants(filePath) { customFilePath = filePath; cache = {}; cacheLoadedAt = 0; cacheMtimeMs = -1; cacheValid = false; } function getFilePath() { return customFilePath ?? join(homedir(), ".neurolink", RESIDENT_FILE); } function isResidentGrant(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; return (typeof candidate.accountLabel === "string" && typeof candidate.grantId === "string" && typeof candidate.leaseSecret === "string" && !!candidate.lease); } /** * Is this error simply "the file is not there yet"? * * The distinction is load-bearing. An absent file genuinely is an empty map — * nothing has been written yet. Every *other* `stat`/read failure (`EACCES`, * `EIO`, `EMFILE`, a full descriptor table) is a failure to observe the file, * and answering one with an empty map is how a whole store gets erased: a * caller passing `force` is about to `persist()` the map back over the real * contents it just failed to read. */ function isMissingFileError(error) { return error?.code === "ENOENT"; } async function ensureLoaded(options = {}) { const now = Date.now(); if (!options.force && cacheValid && now - cacheLoadedAt < RELOAD_TTL_MS) { return; } const path = getFilePath(); let mtimeMs; try { mtimeMs = (await stat(path)).mtimeMs; } catch (error) { if (!isMissingFileError(error)) { // Not "no file" but "could not look" — see `isMissingFileError`. Let it // out: a mutation must abort rather than persist an empty map over a // store it never managed to read. throw error; } cache = {}; cacheMtimeMs = -1; cacheLoadedAt = now; cacheValid = true; return; } // A forced load skips this. mtime is the fast path for a read, not a // correctness check for a write: several filesystems stamp it at one-second // granularity, so a write landing in the same second as our last read is // indistinguishable from no write at all — and every caller passing `force` // is about to persist the whole map back over whatever it missed. if (!options.force && cacheValid && mtimeMs === cacheMtimeMs) { cacheLoadedAt = now; return; } try { const parsed = JSON.parse(await readFile(path, "utf8")); cache = Object.fromEntries(Object.entries(parsed?.grants ?? {}).filter((entry) => isResidentGrant(entry[1]))); } catch (error) { if (options.force) { // A mutation is about to write the whole map back; treating a corrupt // file as empty here would make that write finish the corruption off. // Abort instead and leave the file for a human. Reads stay tolerant. throw error; } cache = {}; } cacheMtimeMs = mtimeMs; cacheLoadedAt = now; cacheValid = true; } async function persist() { const file = { schemaVersion: 1, grants: cache }; await writeJsonSnapshotAtomically(getFilePath(), file); try { cacheMtimeMs = (await stat(getFilePath())).mtimeMs; } catch { cacheMtimeMs = -1; } cacheLoadedAt = Date.now(); cacheValid = true; } /** * Narrow a heartbeat answer to its stopping half. * Explicit because one build step compiles without `strictNullChecks`, where a * boolean discriminant does not narrow. */ function isHeartbeatStop(payload) { return !payload.ok; } /** * Is this shaped like a lease at all? * * Shape only — {@link isLeaseAuthentic} decides whether it is *ours*. Both run * before a renewal is allowed to replace a lease that currently works, because * the answer comes off the wire and an `{ ok: true }` with no usable lease * would otherwise be persisted over the one keeping this account alive. */ function isShareLease(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; return (typeof candidate.grantId === "string" && typeof candidate.signature === "string" && typeof candidate.issuedAt === "number" && typeof candidate.notAfter === "number" && typeof candidate.heartbeatEveryMs === "number" && typeof candidate.offlineGraceMs === "number" && !!candidate.gates); } /** The matching predicate for the renewing half. */ function isHeartbeatRenewal(payload) { return payload.ok === true && isShareLease(payload.lease); } /** Key by the account label, since that is how the routing path addresses it. */ function keyFor(accountLabel) { return accountLabel.toLowerCase(); } export async function listResidentGrants() { await ensureLoaded(); return Object.values(cache); } export async function getResidentGrantForAccount(accountKeyOrLabel) { await ensureLoaded(); const value = accountKeyOrLabel.toLowerCase(); const label = value.includes(":") ? value.slice(value.indexOf(":") + 1) : value; return cache[keyFor(label)]; } export async function saveResidentGrant(grant) { await mutationMutex.runExclusive(async () => { // Force: `persist()` writes the whole map back, so a mutation that ran on a // TTL-fresh snapshot would resurrect a grant the CLI removed in the window // since this process last read the file. await ensureLoaded({ force: true }); cache[keyFor(grant.accountLabel)] = grant; await persist(); }); } export async function removeResidentGrant(accountLabel) { return mutationMutex.runExclusive(async () => { await ensureLoaded({ force: true }); const key = keyFor(accountLabel); if (!cache[key]) { return false; } delete cache[key]; await persist(); return true; }); } /** * May this resident account serve right now? * * Returns `undefined` for an account that is not resident at all — the node's * own credentials, which answer to nobody. */ export async function evaluateResidentAccount(accountKeyOrLabel, now = Date.now()) { const resident = await getResidentGrantForAccount(accountKeyOrLabel); if (!resident) { return undefined; } return evaluateLease({ lease: resident.lease, secret: resident.leaseSecret, ...(resident.lastHeartbeatAt !== undefined ? { lastHeartbeatAt: resident.lastHeartbeatAt } : {}), now, }); } /** Accumulate spend the borrower owes the lender an account of. */ export async function recordResidentSpend(accountLabel, coins) { await mutationMutex.runExclusive(async () => { await ensureLoaded({ force: true }); const key = keyFor(accountLabel); const resident = cache[key]; if (!resident) { return; } cache[key] = { ...resident, unreportedCoins: (resident.unreportedCoins ?? 0) + coins, unreportedRequests: (resident.unreportedRequests ?? 0) + 1, }; await persist(); }); } /** * Apply a heartbeat's outcome to whatever the record says **now**. * * The round trip takes up to {@link HEARTBEAT_TIMEOUT_MS} and requests keep * being served throughout it, so writing back the snapshot the heartbeat * started from would silently drop every coin {@link recordResidentSpend} * booked in that window. The reported amounts are subtracted from the current * counters instead of the counters being zeroed, and the record is re-read * rather than reconstructed from the caller's copy. */ async function settleHeartbeat(args) { await mutationMutex.runExclusive(async () => { await ensureLoaded({ force: true }); const key = keyFor(args.accountLabel); const current = cache[key]; if (!current) { return; } cache[key] = { ...current, lease: args.outcome === "stop" ? // Force the next evaluation to fail closed without waiting out grace. { ...current.lease, notAfter: args.now } : args.outcome.lease, lastHeartbeatAt: args.now, unreportedCoins: Math.max(0, (current.unreportedCoins ?? 0) - args.reportedCoins), unreportedRequests: Math.max(0, (current.unreportedRequests ?? 0) - args.reportedRequests), }; await persist(); }); } /** * Check in with a lender: report what was spent, collect a fresh lease. * * Reporting happens **before** the new lease is stored, and the counters are * only drawn down once the lender has acknowledged them — a heartbeat that * fails halfway leaves the spend to be reported again rather than losing it. * * A `stop` answer is honored immediately by clearing the lease's grace: the * lender has said no, and there is nothing to wait out. */ export async function heartbeatResidentGrant(resident, now = Date.now()) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), HEARTBEAT_TIMEOUT_MS); // Exactly what this round trip claims to have reported, so the settle can // subtract that and nothing more. const reportedCoins = resident.unreportedCoins ?? 0; const reportedRequests = resident.unreportedRequests ?? 0; try { const response = await fetch(`${resident.lenderUrl}/peer/heartbeat`, { method: "POST", headers: { "content-type": "application/json", "x-neurolink-share-token": resident.leaseSecret, "x-neurolink-grant-id": resident.grantId, }, body: JSON.stringify({ grantId: resident.grantId, coinsSpent: reportedCoins, requests: reportedRequests, reportedAt: now, }), signal: controller.signal, }); if (!response.ok) { return { ok: false, stopped: false, detail: `lender answered ${response.status}`, }; } const payload = (await response.json()); if (isHeartbeatStop(payload)) { await settleHeartbeat({ accountLabel: resident.accountLabel, reportedCoins, reportedRequests, outcome: "stop", now, }); logger.always(`[proxy] lender stopped resident grant ${resident.accountLabel}: ${payload.reason}`); return { ok: true, stopped: true, detail: payload.reason }; } if (!isHeartbeatRenewal(payload)) { return { ok: false, stopped: false, detail: "lender answered with neither a lease nor a stop", }; } // A renewal that is not signed by the secret we already hold, or that is // for some other grant, is not a renewal — keep the working lease and let // the offline grace run. Replacing it would hand any party that can answer // this request the ability to revoke us, or to extend us indefinitely. if (payload.lease.grantId !== resident.grantId) { return { ok: false, stopped: false, detail: "renewed lease is for a different grant", }; } if (!isLeaseAuthentic(payload.lease, resident.leaseSecret)) { return { ok: false, stopped: false, detail: "renewed lease is not signed by the lender's secret", }; } await settleHeartbeat({ accountLabel: resident.accountLabel, reportedCoins, reportedRequests, outcome: { lease: payload.lease }, now, }); return { ok: true, stopped: false, detail: "lease renewed" }; } catch (error) { return { ok: false, stopped: false, detail: error instanceof Error ? error.message : String(error), }; } finally { clearTimeout(timeout); } } /** * Check in with every lender whose heartbeat is due. * * Best-effort by design: a lender being unreachable is the case the offline * grace exists for, not an error to surface. */ export async function heartbeatDueResidentGrants(now = Date.now()) { const grants = await listResidentGrants(); for (const resident of grants) { if (!isHeartbeatDue(resident.lease, resident.lastHeartbeatAt, now)) { continue; } const result = await heartbeatResidentGrant(resident, now); if (!result.ok) { logger.debug(`[proxy] heartbeat to ${resident.lenderName} failed: ${result.detail}`); } } } /** The lease a resident account is currently operating under. */ export function residentLease(resident) { return resident.lease; }