Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ validate-artifact-digest-honesty:
validate-consent-before-staging:
$(PYTHON) scripts/validate-consent-before-staging.py

validate-inference-gateway:
$(PYTHON) scripts/validate-inference-gateway.py

validate-inference-receipt:
$(PYTHON) scripts/validate-inference-receipt.py

Expand Down
63 changes: 63 additions & 0 deletions scripts/validate-inference-gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Validate the local InferenceGateway adapter refuses and audits, fail-closed.

The property asserted is a refusal: a call served without a registered active
provider, or without an admitting consent decision, or without a backend, must not
return output — and must still emit a GatewayCallAudit (outcome="denied"). A gateway
never observed refusing is indistinguishable from no gateway. One positive case
confirms an admitted call returns output plus an outcome="ok" audit.
"""
from __future__ import annotations

import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_ROOT = REPO_ROOT / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))

from agent_machine.inference_gateway import serve, GATEWAY_AUDIT_REQUIRED # noqa: E402

ACTIVE = {"id": "local-ollama", "kind": "InferenceProvider", "status": "active"}
OK_REQ = {
"model": "llama-3.3-70b", "caller": "human:michael", "purpose": "discover",
"space": "user-space", "input": "hi",
"consent": {"policyDecisionRef": "urn:srcos:policy-decision:abc"},
}
def _backend(_req):
return {"output": "hello", "usage": {"tokens": 3}}

failures = []
def check(name, cond):
print((" ok " if cond else " FAIL ") + name)
if not cond:
failures.append(name)

def audit_ok(a):
return isinstance(a, dict) and all(k in a for k in GATEWAY_AUDIT_REQUIRED)

# ── negative controls: every refusal returns no output + a denied audit ──
for name, req, prov, backend in [
("no consent → refused", {**OK_REQ, "consent": {}}, ACTIVE, _backend),
("no provider → refused", OK_REQ, None, _backend),
("inactive provider → refused", OK_REQ, {**ACTIVE, "status": "draft"}, _backend),
("no backend → refused", OK_REQ, ACTIVE, None),
("missing field (model) → refused", {k: v for k, v in OK_REQ.items() if k != "model"}, ACTIVE, _backend),
("non-dict request → refused", "nope", ACTIVE, _backend),
("backend raises → audited denial", OK_REQ, ACTIVE, (lambda _r: (_ for _ in ()).throw(RuntimeError()))),
]:
resp, audit = serve(req, provider=prov, backend=backend)
check(name + " [no output]", resp is None)
check(name + " [denied audit]", audit_ok(audit) and audit["outcome"] == "denied")

# ── positive control: an admitted call returns output + an ok audit ──
resp, audit = serve(OK_REQ, provider=ACTIVE, backend=_backend)
check("admitted call returns output", resp is not None and resp.get("output") == "hello")
check("admitted call ok audit", audit_ok(audit) and audit["outcome"] == "ok")
check("response carries receipt_hash == audit", resp and resp.get("receipt_hash") == audit["receipt_hash"])

if failures:
print(f"\nFAILED: {len(failures)} check(s)")
sys.exit(1)
print("\nOK: InferenceGateway local adapter fails closed and audits every call")
101 changes: 101 additions & 0 deletions src/agent_machine/inference_gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Local adapter for the InferenceGateway intersection seam.

Both planes serve inference through one contract (sourceos-spec
`inference-gateway-intersection.md`); this is the **local** adapter (agent-machine /
Noetica). It serves a call only through a registered, active `InferenceProvider`
(`contracts/inference-provider.schema.json`) and only when an admitting consent
decision is present — and it emits a `GatewayCallAudit` (memory-mesh
`gateway-call-audit.schema.json` v0.1) on **every** call, including refusals, so that
nothing is ever served un-consented or un-audited.

The controls here are refusals. A gateway never observed denying a call is
indistinguishable from no gateway — so a *denied* call still returns a full audit
(outcome="denied") and no output. Output is returned only on outcome="ok".
"""
from __future__ import annotations

import datetime
from typing import Any, Callable, Dict, Optional, Tuple

from agent_machine.digest import stable_digest

# GatewayCallAudit v0.1 required fields (memory-mesh schemas/gateway-call-audit.schema.json).
GATEWAY_AUDIT_REQUIRED = [
"schemaVersion", "recordType", "callId", "call_type", "outcome", "caller",
"model", "epistemicLevel", "recalled_count", "written", "occurred_at", "receipt_hash",
]
_ACTIVE_PROVIDER_STATUS = {"active", "ready", "serving", "enabled"}


def _now() -> str:
return datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def _audit(request: Dict[str, Any], outcome: str, error: Optional[str] = None) -> Dict[str, Any]:
"""Build a GatewayCallAudit record. Always fully populated — even for refusals."""
core = {
"schemaVersion": "0.1",
"recordType": "GatewayCallAudit",
"call_type": "inference",
"outcome": outcome,
"caller": (request or {}).get("caller", "unknown"),
"model": (request or {}).get("model", "unknown"),
"epistemicLevel": (request or {}).get("epistemicLevel", "operational"),
"recalled_count": 0,
"written": False,
"occurred_at": _now(),
}
if error:
core["error"] = error
core["callId"] = stable_digest({"c": core["caller"], "m": core["model"], "t": core["occurred_at"], "o": outcome})[7:23]
core["receipt_hash"] = stable_digest(core)
return core


def _refuse(request: Dict[str, Any], reason: str) -> Tuple[None, Dict[str, Any]]:
return None, _audit(request, "denied", error=reason)


def serve(
request: Dict[str, Any],
*,
provider: Optional[Dict[str, Any]] = None,
backend: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None,
) -> Tuple[Optional[Dict[str, Any]], Dict[str, Any]]:
"""Serve one inference call, fail-closed. Returns (response|None, audit).

Output is returned only when the call is admitted (outcome="ok"); every other
path returns (None, denied-audit). Never raises for a bad request — it refuses.
"""
if not isinstance(request, dict):
return _refuse({}, "request is not an object")
for field in ("model", "caller", "purpose", "space"):
if not request.get(field):
return _refuse(request, f"request missing required field: {field}")

consent = request.get("consent")
if not isinstance(consent, dict) or not consent.get("policyDecisionRef"):
return _refuse(request, "no admitting consent decision — refused fail-closed")

if not isinstance(provider, dict):
return _refuse(request, "no registered InferenceProvider — refused fail-closed")
if provider.get("status") not in _ACTIVE_PROVIDER_STATUS:
return _refuse(request, f"provider status {provider.get('status')!r} is not active")

if not callable(backend):
return _refuse(request, "no serving backend available")

try:
result = backend(request)
except Exception as exc: # a backend failure is an audited denial, not a crash
return _refuse(request, f"backend error: {type(exc).__name__}")
if not isinstance(result, dict) or "output" not in result:
return _refuse(request, "backend returned no output")

audit = _audit(request, "ok")
response = {
"output": result["output"],
"usage": result.get("usage", {}),
"receipt_hash": audit["receipt_hash"],
}
return response, audit
Loading