diff --git a/feat/aegis-sdk-integration b/feat/aegis-sdk-integration new file mode 100644 index 0000000..36bf221 --- /dev/null +++ b/feat/aegis-sdk-integration @@ -0,0 +1,15 @@ +# Drop files into their correct locations: +cp aegis-adapter.mjs sdk/aegis-adapter.mjs +cp CLAUDE.md CLAUDE.md +cp adapter.test.mjs tests/aegis/adapter.test.mjs +cp aegis-health.mjs scripts/aegis-health.mjs +cp crucix-ci.yml .github/workflows/crucix-ci.yml +cp package.json package.json +cp .env.example .env.example + +mkdir -p .claude/skills docs/agent-guides +cp aegis-sdk.md .claude/skills/aegis-sdk.md +cp build-test-verify.md .claude/skills/build-test-verify.md +cp git-commit.md .claude/skills/git-commit.md +cp architecture.md docs/agent-guides/architecture.md +cp aegis-sdk-guide.md docs/agent-guides/aegis-sdk-guide.md \ No newline at end of file diff --git a/sdk/aegis-adapter.mjs b/sdk/aegis-adapter.mjs new file mode 100644 index 0000000..bd2e80a --- /dev/null +++ b/sdk/aegis-adapter.mjs @@ -0,0 +1,123 @@ +/** + * sdk/aegis-adapter.mjs + * Ethos Aegis integration layer for Crucix. + * Uses the native Node.js AegisClient for robust subprocess/HTTP management. + */ + +import { AegisClient } from './ethos-aegis/sdk/node/src/index.js'; + +// ─── Config ─────────────────────────────────────────────────────────────────── + +const SIDECAR_URL = process.env.AEGIS_SIDECAR_URL || 'http://localhost:8080'; +const BEARER_TOKEN = process.env.AEGIS_BEARER_TOKEN || 'local-dev-key'; +const STRICT_MODE = process.env.AEGIS_STRICT_MODE === 'true'; + +// Initialize the real AegisClient +const aegis = new AegisClient({ + baseUrl: SIDECAR_URL, + apiKey: BEARER_TOKEN, + throwOnCondemned: false, // We handle the escalation manually in Crucix + timeoutMs: parseInt(process.env.AEGIS_TIMEOUT_MS || '3000', 10) +}); + +// ─── Severity → Crucix tier mapping ────────────────────────────────────────── + +/** + * Maps an Aegis depth to a Crucix alert tier. + * @param {string} depth + * @returns {'FLASH'|'PRIORITY'|'ROUTINE'|'PASS'} + */ +export function depthToTier(depth) { + switch (depth?.toUpperCase()) { + case 'CONDEMNED': return 'FLASH'; + case 'GRAVE': return 'PRIORITY'; + case 'CAUTION': return 'ROUTINE'; + case 'TRACE': return 'ROUTINE'; + case 'VOID': + default: return 'PASS'; + } +} + +// ─── Guard Functions ───────────────────────────────────────────────────────── + +export async function guardInput(rawText, context = {}) { + try { + const verdict = await aegis.score(rawText, { guardPoint: 'input', ...context }); + return { + sanctified: verdict.sanctified, + payload: verdict.sanitized || rawText, // Fallback to raw if not sanitized + verdict + }; + } catch (err) { + if (STRICT_MODE) throw new Error(`[Aegis] Strict mode active. Blocked due to sidecar failure: ${err.message}`); + console.warn(`[Aegis] Warning: Sidecar unreachable. Failing open. (${err.message})`); + return { sanctified: true, payload: rawText, verdict: _syntheticPass() }; + } +} + +export async function guardOutput(llmResponse, context = {}) { + try { + const verdict = await aegis.score(llmResponse, { guardPoint: 'output', ...context }); + return { + sanctified: verdict.sanctified, + payload: verdict.sanitized || llmResponse, + verdict + }; + } catch (err) { + if (STRICT_MODE) throw new Error(`[Aegis] Output guard failed: ${err.message}`); + return { sanctified: true, payload: llmResponse, verdict: _syntheticPass() }; + } +} + +export async function guardBotCommand(commandText, userId) { + try { + const verdict = await aegis.score(commandText, { guardPoint: 'bot_command', userId }); + return { + sanctified: verdict.sanctified, + payload: verdict.sanitized || commandText, + verdict + }; + } catch (err) { + if (STRICT_MODE) throw new Error(`[Aegis] Bot command guard failed: ${err.message}`); + return { sanctified: true, payload: commandText, verdict: _syntheticPass() }; + } +} + +export async function evaluateSignal(signal) { + const { sanctified, payload, verdict } = await guardInput(signal.content, { + source: signal.source, + feedId: signal.id + }); + + const tier = depthToTier(verdict.depth); + + if (!sanctified && verdict.condemned) { + console.error(`[Aegis] CONDEMNED Signal Blocked. RequestID: ${verdict.requestId}`); + return { + signal: { ...signal, content: '[CONTENT REDACTED BY AEGIS]' }, + tier: 'FLASH', + blocked: true, + verdict, + }; + } + + return { + signal: { ...signal, content: payload ?? signal.content }, + tier: tier === 'PASS' ? null : tier, + blocked: false, + verdict, + }; +} + +function _syntheticPass() { + return { + sanctified: true, + condemned: false, + depth: 'VOID', + malignaCount: 0, + sanitized: null, + report: 'Synthetic pass (sidecar offline)', + latencyMs: 0, + requestId: `synth-${Date.now()}` + }; +}