Spaces:
Running
Running
File size: 10,739 Bytes
518343a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | // 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),
};
}
|