Express (ESM, no build step) service wrapping Ollama embeddings and Qdrant: - POST /v1/embed, /v1/index, /v1/search - GET /health reports each dependency as ok | absent | unobservable with the exact measurement_surface URL and observed_at; overall ok only when all deps are ok, 503 degraded otherwise - upstream failures on /v1/* return 502 marked unobservable — never an empty result set or indexed:0 - defaults PORT=5499, QDRANT_URL=127.0.0.1:6333, OLLAMA_URL=127.0.0.1:11436 (11436 native Ollama; 11434 is the broken Colima forward on this Mac) - node:test unit tests with injected fetch; run with zero live dependencies - README documents endpoints and a reference launchd plist (not installed) Co-Authored-By: Claude Fable 5 <[email protected]>
68 lines
2.4 KiB
JavaScript
68 lines
2.4 KiB
JavaScript
// Three-valued observability primitives.
|
|
//
|
|
// Every probe answer is one of THREE states, never two:
|
|
// "ok" — the probe saw the thing and it is present/working
|
|
// "absent" — the probe SAW the surface and the thing is genuinely missing
|
|
// "unobservable" — the probe COULD NOT SEE (dependency down, timeout, auth
|
|
// failure, endpoint missing). Never collapsed into ok/absent.
|
|
//
|
|
// Every result carries measurement_surface (exactly what was measured) and
|
|
// observed_at (ISO timestamp).
|
|
|
|
export const OK = "ok";
|
|
export const ABSENT = "absent";
|
|
export const UNOBSERVABLE = "unobservable";
|
|
|
|
export function probeResult(status, measurementSurface, extra = {}) {
|
|
return {
|
|
status,
|
|
measurement_surface: measurementSurface,
|
|
observed_at: new Date().toISOString(),
|
|
...extra,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Perform an HTTP GET probe of `url`. Returns a three-valued probeResult:
|
|
* - ok when the response status passes `okWhen` (default: 2xx)
|
|
* - unobservable on network error, timeout, or non-passing HTTP status
|
|
* (a 401/404/500 from the surface means we could not observe the truth,
|
|
* not that the dependency is healthy or absent).
|
|
* `classify(response, bodyText)` may optionally downgrade an HTTP-ok response
|
|
* to ABSENT (e.g. endpoint reachable but required model not installed).
|
|
*/
|
|
export async function httpProbe(url, { fetchImpl = fetch, timeoutMs = 2500, classify } = {}) {
|
|
const started = Date.now();
|
|
let res;
|
|
try {
|
|
res = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
} catch (err) {
|
|
return probeResult(UNOBSERVABLE, url, {
|
|
reason: `fetch failed: ${err?.cause?.code || err?.name || "error"}: ${err?.message}`,
|
|
latency_ms: Date.now() - started,
|
|
});
|
|
}
|
|
const latency = Date.now() - started;
|
|
if (!res.ok) {
|
|
return probeResult(UNOBSERVABLE, url, {
|
|
reason: `HTTP ${res.status} from probe surface`,
|
|
http_status: res.status,
|
|
latency_ms: latency,
|
|
});
|
|
}
|
|
if (classify) {
|
|
let bodyText = "";
|
|
try {
|
|
bodyText = await res.text();
|
|
} catch {
|
|
return probeResult(UNOBSERVABLE, url, {
|
|
reason: "response body unreadable",
|
|
latency_ms: latency,
|
|
});
|
|
}
|
|
const classified = classify(res, bodyText);
|
|
if (classified) return probeResult(classified.status, url, { latency_ms: latency, ...classified.extra });
|
|
}
|
|
return probeResult(OK, url, { http_status: res.status, latency_ms: latency });
|
|
}
|