fastapi-gsap/gsap_broker/functions/base.py
Tyler J King 0c77943ceb feat: governed function runtime + billing drain
GCAP-SPEC-FUNCTION-DESCRIPTOR-0001 implementation.
Mirrors connector runtime pattern exactly.

FunctionPlugin — trigger_events, handle(), descriptor(), knative_manifest()
FunctionRegistry — trigger_index for event-driven routing
FunctionRuntime — invoke() + dispatch() with Chronicle lineage
governed_function decorator — SDK surface for function authors
BillingProcessor — GSAP_CR_RECEIVED → billable event with Chronicle CID
EchoFunction — dev/test

API: /functions/ catalogue, invoke, dispatch, manifest, health
8 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:12:29 -04:00

116 lines
4 KiB
Python

"""FunctionPlugin ABC per GCAP-SPEC-FUNCTION-DESCRIPTOR-0001 section 4."""
from __future__ import annotations
import hashlib
import json
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any
@dataclass
class FunctionContext:
trigger_event_kind: str = ""
trigger_event_cid: str = ""
chronicle_session_id: str = ""
gsap_context_id: str = ""
invocation_id: str = ""
pipeline_run_id: str = ""
@dataclass
class FunctionResult:
success: bool = False
data: Any = None
error: str | None = None
lineage_cid: str = ""
duration_ms: float = 0.0
metadata: dict[str, Any] = field(default_factory=dict)
def output_cid(self) -> str:
"""Compute content-addressable hash of the output data."""
canonical = json.dumps(self.data, sort_keys=True, separators=(",", ":"), default=str)
return "sha256:" + hashlib.sha256(canonical.encode()).hexdigest()
class FunctionPlugin(ABC):
"""Abstract base for governed serverless functions."""
function_id: str = ""
corpus_entry_cid: str = ""
capability_mask: int = 0
trigger_events: list[str] = []
accord_template: str = ""
gsap_required: bool = True
chronicle_enabled: bool = True
max_duration_seconds: int = 30
display_name: str = ""
description: str = ""
version: str = "0.1.0"
@abstractmethod
async def handle(self, event: dict[str, Any], context: FunctionContext) -> FunctionResult:
...
@abstractmethod
def health_check(self) -> bool:
...
def descriptor(self) -> dict[str, Any]:
"""JSON-LD descriptor per GCAP-SPEC-FUNCTION-DESCRIPTOR-0001 §2."""
return {
"@context": "https://schema.gsap.dev/function/v1",
"@type": "FunctionDescriptor",
"function_id": self.function_id,
"corpus_entry_cid": self.corpus_entry_cid,
"capability_mask": self.capability_mask,
"trigger_events": self.trigger_events,
"accord_template": self.accord_template,
"gsap_required": self.gsap_required,
"chronicle_enabled": self.chronicle_enabled,
"max_duration_seconds": self.max_duration_seconds,
"display_name": self.display_name,
"description": self.description,
"version": self.version,
}
def knative_manifest(self, image: str, namespace: str = "default") -> dict[str, Any]:
"""Generate Knative Service manifest."""
return {
"apiVersion": "serving.knative.dev/v1",
"kind": "Service",
"metadata": {
"name": self.function_id,
"namespace": namespace,
"labels": {
"gcap.dev/function-id": self.function_id,
"gcap.dev/capability-mask": str(self.capability_mask),
},
"annotations": {
"gcap.dev/corpus-entry-cid": self.corpus_entry_cid,
"gcap.dev/gsap-required": str(self.gsap_required).lower(),
},
},
"spec": {
"template": {
"metadata": {
"annotations": {
"autoscaling.knative.dev/minScale": "0",
"autoscaling.knative.dev/maxScale": "10",
},
},
"spec": {
"timeoutSeconds": self.max_duration_seconds,
"containers": [
{
"image": image,
"env": [
{"name": "GCAP_FUNCTION_ID", "value": self.function_id},
{"name": "GCAP_CAPABILITY_MASK", "value": str(self.capability_mask)},
],
}
],
},
},
},
}