a11oy / packages /mesh-router /src /index.ts
betterwithage's picture
sync(space): complete build context — fix BUILD_ERROR (CTO)
518343a verified
Raw
History Blame
10.7 kB
// Copyright 2026 SZL Holdings
// SPDX-License-Identifier: Apache-2.0
//
// mesh-router — typed HTTP router from a11oy (the substrate) to its sibling
// organs. a11oy is the skeleton: it routes reasoning to amaru (the brain),
// policy verdicts to sentra (the immune organ), and operator I/O to rosie
// (the operator console). This module is the substrate-side client for those
// network calls.
//
// Source of this instillation: the mesh "wires that SHOULD exist but DON'T"
// findings (MINING_REPORT.md §4 #2 and #3) — a11oy -> sentra delegation and
// a11oy -> amaru memory query had no runtime client in a11oy. This package is
// that client. Strategy: C (wire over HTTP using each producer's existing
// server), per the instillation strategy preference order.
//
// Honesty note (Doctrine v7 §2): amaru exposes a FastAPI sidecar with the
// routes called here (GET /state, GET /receipts, POST /chakra/{name}/evaluate
// — verified in the cross-repo mining server index). Sentra ships
// runtime/immune_server.py (sentra PR #96) exposing POST /v1/inspect — the
// real inbound immune endpoint. The 2026-05-31 post-merge re-test wave found
// Wire B was contract-mismatched (a11oy was calling /v1/verdict; sentra serves
// /v1/inspect). This module now targets the deployed sentra contract:
// path: POST /v1/inspect
// body: {actionId, action, traceparent?}
// resp: {actionId, decision: "allow"|"deny", gate, decidedBy, rationale,
// lambdaScore, receiptHash, traceparent}
// The exposed SentraVerdict shape ({decision, reason}) is preserved for
// existing callers; `reason` is populated from sentra's `rationale` field on
// allow + deny.
//
// No mocked data path: every function issues a real HTTP request via
// node:http/https and parses the real response. The accompanying tests boot a
// real loopback server and exercise these functions over real TCP.
//
// Authored for SZL Holdings. Signed-off per repository DCO.
import * as http from "node:http";
import * as https from "node:https";
/** A resolved endpoint for one sibling organ. */
export interface OrganEndpoint {
/** Base URL, e.g. "http://amaru.svc:8000". No trailing slash required. */
readonly baseUrl: string;
/** Per-request timeout in milliseconds. */
readonly timeoutMs?: number;
}
/** The three organs a11oy routes to. */
export interface MeshConfig {
/** amaru — reasoning / memory (the brain). */
readonly amaru?: OrganEndpoint;
/** sentra — policy / threat verdicts (the immune organ). */
readonly sentra?: OrganEndpoint;
/** rosie — operator I/O (the operator console). */
readonly rosie?: OrganEndpoint;
}
/** A structured transport result; never throws on a non-2xx status. */
export interface MeshResponse<T = unknown> {
readonly ok: boolean;
readonly status: number;
readonly body: T | null;
/** Populated when the call could not complete (timeout, connection, parse). */
readonly error?: string;
}
const DEFAULT_TIMEOUT_MS = 5_000;
function trimSlash(url: string): string {
return url.endsWith("/") ? url.slice(0, -1) : url;
}
/**
* Issue a single JSON HTTP request. Pure transport: it resolves with a
* MeshResponse for any HTTP status, and only sets `error` for transport-level
* failures (DNS, connect, timeout, body parse). It never throws.
*/
export function meshRequest<T = unknown>(
endpoint: OrganEndpoint,
method: "GET" | "POST",
path: string,
payload?: unknown,
): Promise<MeshResponse<T>> {
const url = new URL(trimSlash(endpoint.baseUrl) + path);
const isHttps = url.protocol === "https:";
const lib = isHttps ? https : http;
const data = payload === undefined ? undefined : JSON.stringify(payload);
const timeoutMs = endpoint.timeoutMs ?? DEFAULT_TIMEOUT_MS;
return new Promise<MeshResponse<T>>((resolve) => {
const req = lib.request(
{
protocol: url.protocol,
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + url.search,
method,
headers: {
accept: "application/json",
...(data !== undefined
? {
"content-type": "application/json",
"content-length": Buffer.byteLength(data),
}
: {}),
},
},
(res) => {
const chunks: Buffer[] = [];
res.on("data", (c: Buffer) => chunks.push(c));
res.on("end", () => {
const raw = Buffer.concat(chunks).toString("utf8");
const status = res.statusCode ?? 0;
let body: T | null = null;
let parseError: string | undefined;
if (raw.length > 0) {
try {
body = JSON.parse(raw) as T;
} catch {
parseError = "response body was not valid JSON";
}
}
resolve({
ok: status >= 200 && status < 300 && parseError === undefined,
status,
body,
error: parseError,
});
});
},
);
req.on("error", (err) => {
resolve({ ok: false, status: 0, body: null, error: err.message });
});
req.setTimeout(timeoutMs, () => {
req.destroy();
resolve({ ok: false, status: 0, body: null, error: `timeout after ${timeoutMs}ms` });
});
if (data !== undefined) req.write(data);
req.end();
});
}
// --- amaru: the brain (reasoning / memory) ---------------------------------
export interface AmaruState {
readonly [key: string]: unknown;
}
/** Query amaru's current memory/overwatch state (GET /state). */
export function queryAmaruState(cfg: MeshConfig): Promise<MeshResponse<AmaruState>> {
if (!cfg.amaru) {
return Promise.resolve({ ok: false, status: 0, body: null, error: "amaru endpoint not configured" });
}
return meshRequest<AmaruState>(cfg.amaru, "GET", "/state");
}
/** Read amaru's published receipts (GET /receipts). */
export function queryAmaruReceipts(cfg: MeshConfig): Promise<MeshResponse> {
if (!cfg.amaru) {
return Promise.resolve({ ok: false, status: 0, body: null, error: "amaru endpoint not configured" });
}
return meshRequest(cfg.amaru, "GET", "/receipts");
}
/** Ask amaru to evaluate a named chakra (POST /chakra/{name}/evaluate). */
export function evaluateAmaruChakra(
cfg: MeshConfig,
chakra: string,
input: unknown,
): Promise<MeshResponse> {
if (!cfg.amaru) {
return Promise.resolve({ ok: false, status: 0, body: null, error: "amaru endpoint not configured" });
}
return meshRequest(cfg.amaru, "POST", `/chakra/${encodeURIComponent(chakra)}/evaluate`, input);
}
// --- sentra: the immune organ (policy / threat verdicts) -------------------
export interface SentraVerdictRequest {
readonly actionId: string;
readonly kind: "egress" | "threat" | "admission";
/** Arbitrary action payload sentra inspects (cmd, url, headers, etc.). */
readonly payload: unknown;
/** Optional W3C traceparent so the immune call joins the parent trace. */
readonly traceparent?: string;
}
/**
* Externally exposed verdict shape — kept small for callers. The full sentra
* response is mapped down to {decision, reason} via inspectResponseToVerdict.
*/
export interface SentraVerdict {
readonly decision: "allow" | "deny";
readonly reason?: string;
}
/**
* Sentra's /v1/inspect response shape (anatomy-contracts PolicyDecision). We
* declare it locally rather than importing to keep mesh-router dependency-free.
*/
interface SentraInspectResponse {
readonly actionId: string;
readonly decision: "allow" | "deny";
readonly gate?: string;
readonly decidedBy?: string;
readonly rationale?: string;
readonly lambdaScore?: number;
readonly receiptHash?: string;
readonly traceparent?: string;
}
function inspectResponseToVerdict(r: SentraInspectResponse): SentraVerdict {
return {
decision: r.decision,
reason: r.rationale,
};
}
/**
* Translate the public SentraVerdictRequest to sentra's actual /v1/inspect
* body. sentra accepts `{action, actionId, traceparent}` — the `kind` field on
* our public request is folded into `action.kind` so sentra's signature-scan
* can still see it.
*/
function verdictRequestToInspectBody(req: SentraVerdictRequest): {
action: Record<string, unknown>;
actionId: string;
traceparent?: string;
} {
const action: Record<string, unknown> =
req.payload && typeof req.payload === "object" && req.payload !== null
? { ...(req.payload as Record<string, unknown>), kind: req.kind }
: { payload: req.payload, kind: req.kind };
const body: { action: Record<string, unknown>; actionId: string; traceparent?: string } = {
action,
actionId: req.actionId,
};
if (req.traceparent) body.traceparent = req.traceparent;
return body;
}
/**
* Request an admission/egress verdict from sentra before a11oy admits an
* action. Targets sentra's deployed POST /v1/inspect route. Fails closed
* (deny) on transport error, missing config, or any non-2xx — so a missing or
* crashed immune organ never silently allows.
*/
export async function requestSentraVerdict(
cfg: MeshConfig,
request: SentraVerdictRequest,
): Promise<SentraVerdict> {
if (!cfg.sentra) {
return { decision: "deny", reason: "sentra endpoint not configured (fail closed)" };
}
const body = verdictRequestToInspectBody(request);
const res = await meshRequest<SentraInspectResponse>(
cfg.sentra,
"POST",
"/v1/inspect",
body,
);
if (!res.ok || res.body === null) {
return { decision: "deny", reason: res.error ?? `sentra returned ${res.status} (fail closed)` };
}
// Defensive: if sentra returned an unexpected shape, fail closed.
if (res.body.decision !== "allow" && res.body.decision !== "deny") {
return { decision: "deny", reason: `sentra returned unrecognised decision (fail closed)` };
}
return inspectResponseToVerdict(res.body);
}
// --- rosie: the operator console (operator I/O) ----------------------------
/** Push an operator-facing event to rosie (POST /v1/events). */
export function notifyRosie(cfg: MeshConfig, event: unknown): Promise<MeshResponse> {
if (!cfg.rosie) {
return Promise.resolve({ ok: false, status: 0, body: null, error: "rosie endpoint not configured" });
}
return meshRequest(cfg.rosie, "POST", "/v1/events", event);
}
/** Build a MeshConfig from environment variables (all optional). */
export function meshConfigFromEnv(env: NodeJS.ProcessEnv = process.env): MeshConfig {
const opt = (v: string | undefined): OrganEndpoint | undefined =>
v && v.trim() ? { baseUrl: v.trim() } : undefined;
return {
amaru: opt(env.A11OY_AMARU_URL),
sentra: opt(env.A11OY_SENTRA_URL),
rosie: opt(env.A11OY_ROSIE_URL),
};
}