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>
116 lines
4 KiB
Python
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)},
|
|
],
|
|
}
|
|
],
|
|
},
|
|
},
|
|
},
|
|
}
|