"""Scope-bound governance for traces, Tool/MCP calls, and credentials. This module deliberately stores references and audit metadata only. Secret material remains in the configured credential provider boundary. """ from __future__ import annotations from dataclasses import asdict, dataclass, field from datetime import datetime, timezone from contextlib import contextmanager import hashlib import json import re import sqlite3 from pathlib import Path from typing import Any, Iterable, Mapping SCOPE_FIELDS = ("tenant_id", "user_id", "project_id", "conversation_id", "run_id") PRIVILEGED_CAPABILITIES = frozenset({"*", "admin", "system.admin", "governance.admin"}) REFERENCE_RE = re.compile(r"^(systemd-credential|vault|secret-manager):[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$") IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$") class GovernanceError(RuntimeError): def __init__(self, code: str, *, field: str | None = None) -> None: super().__init__(code if field is None else f"{code}:{field}") self.code = code self.field = field def _clean(value: Any) -> str | None: if value is None: return None text = str(value).strip() return text or None def _scope_tuple(value: Iterable[str] | str | None) -> tuple[str, ...]: if value is None: return () if isinstance(value, str): value = value.split(",") return tuple(sorted({_clean(item) for item in value if _clean(item)})) def _canonical(value: Any) -> str: return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, default=str) def _sha(value: Any) -> str: return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest() def _now() -> str: return datetime.now(timezone.utc).isoformat() def _identifier(value: Any, code: str) -> str: text = _clean(value) if text is None or IDENTIFIER_RE.fullmatch(text) is None: raise GovernanceError(code) return text @dataclass(frozen=True) class GovernanceScope: """Explicit resource scope; system/global resources never get fake tenants.""" scope_type: str tenant_id: str | None = None user_id: str | None = None project_id: str | None = None conversation_id: str | None = None run_id: str | None = None def __post_init__(self) -> None: object.__setattr__(self, "scope_type", (_clean(self.scope_type) or "").lower()) for name in SCOPE_FIELDS: object.__setattr__(self, name, _clean(getattr(self, name))) if self.scope_type == "tenant": if self.tenant_id is None: raise GovernanceError("GOVERNANCE_TENANT_ID_REQUIRED") elif self.scope_type in {"system", "global"}: if any(getattr(self, name) is not None for name in SCOPE_FIELDS): raise GovernanceError("GOVERNANCE_NON_TENANT_SCOPE_INVALID") elif self.scope_type == "legacy": if any(getattr(self, name) is not None for name in SCOPE_FIELDS): raise GovernanceError("GOVERNANCE_LEGACY_SCOPE_INVALID") else: raise GovernanceError("GOVERNANCE_SCOPE_TYPE_INVALID") @classmethod def from_value(cls, value: Any) -> "GovernanceScope": def get(name: str) -> Any: return value.get(name) if isinstance(value, Mapping) else getattr(value, name, None) declared = _clean(get("scope_type")) tenant_id = _clean(get("tenant_id")) return cls( scope_type=declared or ("tenant" if tenant_id else "legacy"), tenant_id=tenant_id, user_id=get("user_id"), project_id=get("project_id"), conversation_id=get("conversation_id"), run_id=get("run_id"), ) def as_dict(self) -> dict[str, Any]: return asdict(self) @dataclass(frozen=True) class GovernanceActor: identity: str scope: GovernanceScope capabilities: tuple[str, ...] = () def __post_init__(self) -> None: object.__setattr__(self, "identity", _identifier(self.identity, "GOVERNANCE_ACTOR_INVALID")) object.__setattr__(self, "capabilities", _scope_tuple(self.capabilities)) @classmethod def from_value(cls, value: Any) -> "GovernanceActor": if isinstance(value, cls): return value if not isinstance(value, Mapping): raise GovernanceError("GOVERNANCE_ACTOR_INVALID") return cls( identity=value.get("identity") or value.get("actor_id") or value.get("user_id"), scope=GovernanceScope.from_value(value), capabilities=_scope_tuple(value.get("capabilities") or value.get("capability_scope")), ) @property def privileged(self) -> bool: return bool(set(self.capabilities) & PRIVILEGED_CAPABILITIES) def authorize_scope(actor: GovernanceActor | Mapping[str, Any], resource: GovernanceScope | Mapping[str, Any]) -> None: actor = GovernanceActor.from_value(actor) resource = resource if isinstance(resource, GovernanceScope) else GovernanceScope.from_value(resource) if actor.privileged: return if resource.scope_type in {"system", "global"}: raise GovernanceError("GOVERNANCE_SYSTEM_SCOPE_AUTHZ_DENY") if resource.scope_type == "legacy": if actor.scope.scope_type != "legacy": raise GovernanceError("GOVERNANCE_LEGACY_SCOPE_AUTHZ_DENY") return if actor.scope.scope_type != "tenant": raise GovernanceError("GOVERNANCE_SCOPE_MISMATCH_DENY", field="scope_type") for name in SCOPE_FIELDS: expected = getattr(resource, name) actual = getattr(actor.scope, name) if expected is not None and expected != actual: raise GovernanceError("GOVERNANCE_SCOPE_MISMATCH_DENY", field=name) @dataclass(frozen=True) class TraceContext: trace_id: str span_id: str scope: GovernanceScope parent_span_id: str | None = None correlation_id: str | None = None causation_id: str | None = None task_id: str | None = None claim_id: str | None = None message_id: str | None = None def __post_init__(self) -> None: object.__setattr__(self, "trace_id", _identifier(self.trace_id, "TRACE_ID_INVALID")) object.__setattr__(self, "span_id", _identifier(self.span_id, "TRACE_SPAN_ID_INVALID")) for name in ("parent_span_id", "correlation_id", "causation_id", "task_id", "claim_id", "message_id"): value = _clean(getattr(self, name)) if value is not None and IDENTIFIER_RE.fullmatch(value) is None: raise GovernanceError("TRACE_LINEAGE_IDENTIFIER_INVALID", field=name) object.__setattr__(self, name, value) def child(self, *, span_id: str, actor: GovernanceActor | Mapping[str, Any], **lineage: Any) -> "TraceContext": authorize_scope(actor, self.scope) for name in ("trace_id", "task_id", "claim_id", "message_id", "correlation_id"): supplied = _clean(lineage.get(name)) expected = getattr(self, name) if supplied is not None and expected is not None and supplied != expected: raise GovernanceError("TRACE_LINEAGE_CONTINUITY", field=name) requested_scope = lineage.pop("scope", self.scope) requested_scope = requested_scope if isinstance(requested_scope, GovernanceScope) else GovernanceScope.from_value(requested_scope) if requested_scope != self.scope: raise GovernanceError("TRACE_SCOPE_MISMATCH_DENY") return TraceContext( trace_id=self.trace_id, span_id=span_id, parent_span_id=self.span_id, correlation_id=self.correlation_id, causation_id=_clean(lineage.get("causation_id")) or self.span_id, task_id=self.task_id, claim_id=self.claim_id, message_id=self.message_id, scope=self.scope, ) def as_dict(self) -> dict[str, Any]: return { "trace_id": self.trace_id, "span_id": self.span_id, "parent_span_id": self.parent_span_id, "correlation_id": self.correlation_id, "causation_id": self.causation_id, "task_id": self.task_id, "claim_id": self.claim_id, "message_id": self.message_id, **self.scope.as_dict(), } def bind_trace_lineage( *, actor: GovernanceActor | Mapping[str, Any], task: Any, message: Any, trace_id: str, span_id: str, parent: TraceContext | None = None, ) -> TraceContext: actor = GovernanceActor.from_value(actor) task_scope = GovernanceScope.from_value(task) message_scope = GovernanceScope.from_value(message) if task_scope != message_scope: raise GovernanceError("TRACE_SCOPE_MISMATCH_DENY") authorize_scope(actor, task_scope) def get(value: Any, name: str) -> Any: return value.get(name) if isinstance(value, Mapping) else getattr(value, name, None) task_id = _clean(get(task, "task_id")) message_task = _clean(get(message, "task_id")) if task_id and message_task and task_id != message_task: raise GovernanceError("TRACE_LINEAGE_CONTINUITY", field="task_id") context = TraceContext( trace_id=trace_id, span_id=span_id, parent_span_id=parent.span_id if parent else None, correlation_id=_clean(get(task, "correlation_id")) or _clean(get(message, "correlation_id")), causation_id=_clean(get(message, "causation_id")) or (parent.span_id if parent else None), task_id=task_id or message_task, claim_id=_clean(get(task, "claim_id")) or _clean(get(message, "claim_id")), message_id=_clean(get(message, "envelope_id")) or _clean(get(message, "id")), scope=task_scope, ) if parent is not None: if parent.trace_id != context.trace_id or parent.scope != context.scope: raise GovernanceError("TRACE_LINEAGE_CONTINUITY") if parent.task_id and context.task_id and parent.task_id != context.task_id: raise GovernanceError("TRACE_LINEAGE_CONTINUITY", field="task_id") return context @dataclass(frozen=True) class ToolDescriptor: name: str kind: str scope: GovernanceScope required_capabilities: tuple[str, ...] = () credential_ref: str | None = None def __post_init__(self) -> None: object.__setattr__(self, "name", _identifier(self.name, "TOOL_NAME_INVALID")) object.__setattr__(self, "kind", (_clean(self.kind) or "").lower()) if self.kind not in {"tool", "mcp"}: raise GovernanceError("TOOL_KIND_INVALID") object.__setattr__(self, "required_capabilities", _scope_tuple(self.required_capabilities)) if self.credential_ref is not None and REFERENCE_RE.fullmatch(self.credential_ref) is None: raise GovernanceError("CREDENTIAL_SECRET_EXPOSURE_DENY") @dataclass(frozen=True) class CredentialGrant: grant_id: str credential_ref: str scope: GovernanceScope operations: tuple[str, ...] task_id: str | None = None tool_name: str | None = None expires_at: str | None = None def __post_init__(self) -> None: object.__setattr__(self, "grant_id", _identifier(self.grant_id, "CREDENTIAL_GRANT_ID_INVALID")) if REFERENCE_RE.fullmatch(self.credential_ref or "") is None: raise GovernanceError("CREDENTIAL_SECRET_EXPOSURE_DENY") object.__setattr__(self, "operations", _scope_tuple(self.operations)) if not self.operations: raise GovernanceError("CREDENTIAL_OPERATIONS_REQUIRED") if self.expires_at is not None: parsed = datetime.fromisoformat(self.expires_at.replace("Z", "+00:00")) if parsed.tzinfo is None: raise GovernanceError("CREDENTIAL_EXPIRY_INVALID") def authorize( self, actor: GovernanceActor | Mapping[str, Any], *, operation: str, task_id: str | None = None, tool_name: str | None = None, now: datetime | None = None, ) -> str: try: authorize_scope(actor, self.scope) except GovernanceError as exc: raise GovernanceError("CREDENTIAL_SCOPE_DENY", field=exc.field) from exc if operation not in self.operations: raise GovernanceError("CREDENTIAL_SCOPE_DENY", field="operation") if self.task_id is not None and self.task_id != _clean(task_id): raise GovernanceError("CREDENTIAL_SCOPE_DENY", field="task_id") if self.tool_name is not None and self.tool_name != _clean(tool_name): raise GovernanceError("CREDENTIAL_SCOPE_DENY", field="tool_name") if self.expires_at is not None: current = (now or datetime.now(timezone.utc)).astimezone(timezone.utc) expiry = datetime.fromisoformat(self.expires_at.replace("Z", "+00:00")).astimezone(timezone.utc) if current >= expiry: raise GovernanceError("CREDENTIAL_SCOPE_DENY", field="expires_at") return self.credential_ref def public(self) -> dict[str, Any]: return { "grant_id": self.grant_id, "credential_ref": self.credential_ref, "operations": list(self.operations), "task_id": self.task_id, "tool_name": self.tool_name, "expires_at": self.expires_at, **self.scope.as_dict(), } _SCHEMA = """ CREATE TABLE IF NOT EXISTS governance_schema_metadata ( singleton INTEGER PRIMARY KEY CHECK(singleton=1), schema_version INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS governed_tools ( name TEXT PRIMARY KEY, kind TEXT NOT NULL, scope_json TEXT NOT NULL, required_capabilities_json TEXT NOT NULL, credential_ref TEXT, descriptor_sha256 TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS governed_trace_spans ( trace_id TEXT NOT NULL, span_id TEXT NOT NULL, parent_span_id TEXT, context_json TEXT NOT NULL, context_sha256 TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY(trace_id, span_id) ); CREATE TABLE IF NOT EXISTS governed_credential_grants ( grant_id TEXT PRIMARY KEY, grant_json TEXT NOT NULL, grant_sha256 TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS governed_tool_invocations ( invocation_key TEXT PRIMARY KEY, request_sha256 TEXT NOT NULL, tool_name TEXT NOT NULL, task_id TEXT NOT NULL, claim_id TEXT NOT NULL, trace_id TEXT NOT NULL, span_id TEXT NOT NULL, status TEXT NOT NULL, receipt_sha256 TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS governance_audit_evidence ( sequence INTEGER PRIMARY KEY AUTOINCREMENT, evidence_id TEXT NOT NULL UNIQUE, action TEXT NOT NULL, actor_identity TEXT NOT NULL, trace_id TEXT, span_id TEXT, resource_id TEXT NOT NULL, outcome TEXT NOT NULL, details_json TEXT NOT NULL, previous_sha256 TEXT NOT NULL, evidence_sha256 TEXT NOT NULL, created_at TEXT NOT NULL ); """ class GovernanceStore: def __init__(self, path: str | Path) -> None: self.path = Path(path) self.path.parent.mkdir(parents=True, exist_ok=True) with self._connection() as connection: has_metadata = connection.execute( "SELECT 1 FROM sqlite_master WHERE type='table' AND name='governance_schema_metadata'" ).fetchone() if has_metadata: row = connection.execute( "SELECT schema_version FROM governance_schema_metadata WHERE singleton=1" ).fetchone() if row is not None and int(row[0]) != 1: raise GovernanceError("GOVERNANCE_SCHEMA_VERSION_UNSUPPORTED") connection.executescript(_SCHEMA) row = connection.execute( "SELECT schema_version FROM governance_schema_metadata WHERE singleton=1" ).fetchone() if row is None: connection.execute( "INSERT INTO governance_schema_metadata(singleton,schema_version) VALUES(1,1)" ) connection.commit() def _connect(self) -> sqlite3.Connection: connection = sqlite3.connect(self.path, timeout=30.0) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys=ON") connection.execute("PRAGMA busy_timeout=30000") return connection @contextmanager def _connection(self): connection = self._connect() try: yield connection connection.commit() except Exception: connection.rollback() raise finally: connection.close() def _audit( self, connection: sqlite3.Connection, *, action: str, actor: GovernanceActor, resource_id: str, outcome: str, trace: TraceContext | None, details: Mapping[str, Any], ) -> dict[str, Any]: previous = connection.execute( "SELECT evidence_sha256 FROM governance_audit_evidence ORDER BY sequence DESC LIMIT 1" ).fetchone() previous_sha = str(previous[0]) if previous else "" created_at = _now() material = { "action": action, "actor_identity": actor.identity, "trace_id": trace.trace_id if trace else None, "span_id": trace.span_id if trace else None, "resource_id": resource_id, "outcome": outcome, "details": dict(details), "previous_sha256": previous_sha, "created_at": created_at, } digest = _sha(material) evidence_id = "gev-" + digest[:32] connection.execute( """INSERT INTO governance_audit_evidence (evidence_id,action,actor_identity,trace_id,span_id,resource_id,outcome, details_json,previous_sha256,evidence_sha256,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)""", ( evidence_id, action, actor.identity, material["trace_id"], material["span_id"], resource_id, outcome, _canonical(material["details"]), previous_sha, digest, created_at, ), ) return {"evidence_id": evidence_id, "evidence_sha256": digest, **material} def record_trace(self, trace: TraceContext, actor: GovernanceActor | Mapping[str, Any]) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) authorize_scope(actor, trace.scope) context = trace.as_dict() digest = _sha(context) with self._connection() as connection: existing = connection.execute( "SELECT context_sha256 FROM governed_trace_spans WHERE trace_id=? AND span_id=?", (trace.trace_id, trace.span_id), ).fetchone() if existing is not None and existing[0] != digest: raise GovernanceError("TRACE_LINEAGE_CONFLICT") if existing is not None: return {"context": context, "context_sha256": digest, "idempotent": True} if trace.parent_span_id: parent = connection.execute( "SELECT context_json FROM governed_trace_spans WHERE trace_id=? AND span_id=?", (trace.trace_id, trace.parent_span_id), ).fetchone() if parent is None: raise GovernanceError("TRACE_LINEAGE_CONTINUITY", field="parent_span_id") parent_context = json.loads(parent[0]) if GovernanceScope.from_value(parent_context) != trace.scope: raise GovernanceError("TRACE_SCOPE_MISMATCH_DENY") connection.execute( "INSERT OR IGNORE INTO governed_trace_spans VALUES (?,?,?,?,?,?)", (trace.trace_id, trace.span_id, trace.parent_span_id, _canonical(context), digest, _now()), ) evidence = self._audit( connection, action="TRACE_RECORDED", actor=actor, resource_id=f"{trace.trace_id}:{trace.span_id}", outcome="ALLOW", trace=trace, details={"context_sha256": digest}, ) return {"context": context, "context_sha256": digest, "evidence": evidence} def register_tool(self, descriptor: ToolDescriptor, actor: GovernanceActor | Mapping[str, Any]) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) if not actor.privileged: raise GovernanceError("TOOL_MCP_AUTHZ_DENY") public = { "name": descriptor.name, "kind": descriptor.kind, "required_capabilities": list(descriptor.required_capabilities), "credential_ref": descriptor.credential_ref, **descriptor.scope.as_dict(), } digest = _sha(public) with self._connection() as connection: existing = connection.execute( "SELECT descriptor_sha256 FROM governed_tools WHERE name=?", (descriptor.name,) ).fetchone() if existing is not None and existing[0] != digest: raise GovernanceError("TOOL_DESCRIPTOR_CONFLICT") if existing is not None: return {"descriptor": public, "descriptor_sha256": digest, "idempotent": True} connection.execute( "INSERT OR IGNORE INTO governed_tools VALUES (?,?,?,?,?,?)", (descriptor.name, descriptor.kind, _canonical(descriptor.scope.as_dict()), _canonical(descriptor.required_capabilities), descriptor.credential_ref, digest), ) evidence = self._audit( connection, action="TOOL_REGISTERED", actor=actor, resource_id=descriptor.name, outcome="ALLOW", trace=None, details={"descriptor_sha256": digest}, ) return {"descriptor": public, "descriptor_sha256": digest, "evidence": evidence} def authorize_tool( self, *, actor: GovernanceActor | Mapping[str, Any], tool_name: str, trace: TraceContext, requested_scope: GovernanceScope | Mapping[str, Any], input_digest: str, invocation_key: str, ) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) invocation_key = _identifier(invocation_key, "TOOL_INVOCATION_KEY_INVALID") if not re.fullmatch(r"[0-9a-f]{64}", input_digest): raise GovernanceError("TOOL_INPUT_DIGEST_INVALID") if trace.task_id is None or trace.claim_id is None: raise GovernanceError("TOOL_MCP_AUTHZ_DENY", field="active_claim") requested_scope = requested_scope if isinstance(requested_scope, GovernanceScope) else GovernanceScope.from_value(requested_scope) if requested_scope != trace.scope: raise GovernanceError("TOOL_SCOPE_PROPAGATION") authorize_scope(actor, requested_scope) with self._connection() as connection: persisted_trace = connection.execute( "SELECT context_sha256 FROM governed_trace_spans WHERE trace_id=? AND span_id=?", (trace.trace_id, trace.span_id), ).fetchone() if persisted_trace is None or persisted_trace[0] != _sha(trace.as_dict()): raise GovernanceError("TOOL_MCP_AUTHZ_DENY", field="trace") row = connection.execute("SELECT * FROM governed_tools WHERE name=?", (tool_name,)).fetchone() if row is None: raise GovernanceError("TOOL_MCP_AUTHZ_DENY") descriptor_scope = GovernanceScope.from_value(json.loads(row["scope_json"])) if descriptor_scope.scope_type == "tenant" and descriptor_scope != requested_scope: raise GovernanceError("TOOL_MCP_AUTHZ_DENY") required = set(json.loads(row["required_capabilities_json"])) if not required.issubset(set(actor.capabilities)): raise GovernanceError("TOOL_MCP_AUTHZ_DENY") request_sha = _sha({ "tool_name": tool_name, "input_digest": input_digest, "task_id": trace.task_id, "claim_id": trace.claim_id, "trace_id": trace.trace_id, "span_id": trace.span_id, "scope": requested_scope.as_dict(), }) replay = connection.execute( "SELECT * FROM governed_tool_invocations WHERE invocation_key=?", (invocation_key,), ).fetchone() if replay is not None: if replay["request_sha256"] != request_sha: raise GovernanceError("TOOL_INVOCATION_IDEMPOTENCY_CONFLICT") if replay["status"] != "VERIFIED": raise GovernanceError("TOOL_INVOCATION_RECONCILIATION_REQUIRED") return { "tool_name": tool_name, "trace_id": trace.trace_id, "span_id": trace.span_id, "invocation_key": invocation_key, "status": "VERIFIED", "receipt_sha256": replay["receipt_sha256"], "idempotent": True, } created_at = _now() connection.execute( """INSERT INTO governed_tool_invocations (invocation_key,request_sha256,tool_name,task_id,claim_id,trace_id,span_id, status,receipt_sha256,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)""", (invocation_key, request_sha, tool_name, trace.task_id, trace.claim_id, trace.trace_id, trace.span_id, "AUTHORIZED", None, created_at, created_at), ) evidence = self._audit( connection, action="TOOL_AUTHORIZED", actor=actor, resource_id=tool_name, outcome="ALLOW", trace=trace, details={"input_sha256": input_digest, "scope_sha256": _sha(requested_scope.as_dict()), "invocation_key": invocation_key}, ) return { "tool_name": tool_name, "trace_id": trace.trace_id, "span_id": trace.span_id, "invocation_key": invocation_key, "status": "AUTHORIZED", "credential_required": row["credential_ref"] is not None, "evidence": evidence, } def record_tool_receipt( self, *, invocation_key: str, result_digest: str, actor: GovernanceActor | Mapping[str, Any], trace: TraceContext, ) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) invocation_key = _identifier(invocation_key, "TOOL_INVOCATION_KEY_INVALID") if not re.fullmatch(r"[0-9a-f]{64}", result_digest): raise GovernanceError("TOOL_RECEIPT_DIGEST_INVALID") authorize_scope(actor, trace.scope) with self._connection() as connection: row = connection.execute( "SELECT * FROM governed_tool_invocations WHERE invocation_key=?", (invocation_key,), ).fetchone() if row is None or row["trace_id"] != trace.trace_id or row["span_id"] != trace.span_id: raise GovernanceError("TOOL_RECEIPT_BINDING_INVALID") if row["status"] == "VERIFIED": if row["receipt_sha256"] != result_digest: raise GovernanceError("TOOL_RECEIPT_CONFLICT") return {"invocation_key": invocation_key, "status": "VERIFIED", "idempotent": True} if row["status"] != "AUTHORIZED": raise GovernanceError("TOOL_INVOCATION_RECONCILIATION_REQUIRED") connection.execute( "UPDATE governed_tool_invocations SET status='VERIFIED',receipt_sha256=?,updated_at=? " "WHERE invocation_key=? AND status='AUTHORIZED'", (result_digest, _now(), invocation_key), ) evidence = self._audit( connection, action="TOOL_RESULT_VERIFIED", actor=actor, resource_id=row["tool_name"], outcome="ALLOW", trace=trace, details={"invocation_key": invocation_key, "result_sha256": result_digest}, ) return {"invocation_key": invocation_key, "status": "VERIFIED", "evidence": evidence} def store_grant(self, grant: CredentialGrant, actor: GovernanceActor | Mapping[str, Any]) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) if not actor.privileged: raise GovernanceError("CREDENTIAL_SCOPE_DENY") public = grant.public() digest = _sha(public) with self._connection() as connection: existing = connection.execute( "SELECT grant_sha256 FROM governed_credential_grants WHERE grant_id=?", (grant.grant_id,) ).fetchone() if existing is not None and existing[0] != digest: raise GovernanceError("CREDENTIAL_GRANT_CONFLICT") connection.execute( "INSERT OR IGNORE INTO governed_credential_grants VALUES (?,?,?,?)", (grant.grant_id, _canonical(public), digest, _now()), ) evidence = self._audit( connection, action="CREDENTIAL_GRANT_STORED", actor=actor, resource_id=grant.grant_id, outcome="ALLOW", trace=None, details={"grant_sha256": digest}, ) return {"grant": public, "grant_sha256": digest, "evidence": evidence} def authorize_credential( self, *, grant_id: str, actor: GovernanceActor | Mapping[str, Any], trace: TraceContext, operation: str, task_id: str | None, tool_name: str | None, now: datetime | None = None, ) -> dict[str, Any]: actor = GovernanceActor.from_value(actor) with self._connection() as connection: row = connection.execute( "SELECT grant_json FROM governed_credential_grants WHERE grant_id=?", (grant_id,) ).fetchone() if row is None: raise GovernanceError("CREDENTIAL_SCOPE_DENY") raw = json.loads(row[0]) grant = CredentialGrant( grant_id=raw["grant_id"], credential_ref=raw["credential_ref"], scope=GovernanceScope.from_value(raw), operations=tuple(raw["operations"]), task_id=raw.get("task_id"), tool_name=raw.get("tool_name"), expires_at=raw.get("expires_at"), ) if trace.scope != grant.scope: raise GovernanceError("CREDENTIAL_SCOPE_DENY", field="trace_scope") reference = grant.authorize( actor, operation=operation, task_id=task_id, tool_name=tool_name, now=now ) evidence = self._audit( connection, action="CREDENTIAL_AUTHORIZED", actor=actor, resource_id=grant_id, outcome="ALLOW", trace=trace, details={"operation": operation, "task_id": task_id, "tool_name": tool_name, "credential_ref_sha256": _sha(reference)}, ) return {"credential_ref": reference, "evidence": evidence} def verify_audit_chain(self, required_actions: Iterable[str] = ()) -> bool: ordered_actions: list[str] = [] previous = "" with self._connection() as connection: rows = connection.execute("SELECT * FROM governance_audit_evidence ORDER BY sequence").fetchall() for row in rows: details = json.loads(row["details_json"]) material = { "action": row["action"], "actor_identity": row["actor_identity"], "trace_id": row["trace_id"], "span_id": row["span_id"], "resource_id": row["resource_id"], "outcome": row["outcome"], "details": details, "previous_sha256": row["previous_sha256"], "created_at": row["created_at"], } if row["previous_sha256"] != previous or row["evidence_sha256"] != _sha(material): return False previous = row["evidence_sha256"] ordered_actions.append(row["action"]) position = 0 for required in required_actions: try: position = ordered_actions.index(required, position) + 1 except ValueError: return False return True __all__ = [ "CredentialGrant", "GovernanceActor", "GovernanceError", "GovernanceScope", "GovernanceStore", "ToolDescriptor", "TraceContext", "authorize_scope", "bind_trace_lineage", ]