fastapi-gsap/gsap_broker/functions/decorator.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

70 lines
2.3 KiB
Python

"""governed_function decorator — wraps async functions as FunctionPlugins."""
from __future__ import annotations
import functools
from typing import Any, Callable, Awaitable
from .base import FunctionContext, FunctionPlugin, FunctionResult
def governed_function(
function_id: str,
corpus_entry_cid: str,
capability_mask: int = 1,
trigger_events: list[str] | None = None,
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",
registry: Any = None,
) -> Callable:
"""Decorator that wraps an async function as a governed FunctionPlugin.
Usage::
@governed_function(
function_id="my-func",
corpus_entry_cid="sha256:abc...",
)
async def my_func(event, context):
return FunctionResult(success=True, data=event)
"""
def decorator(
fn: Callable[[dict[str, Any], FunctionContext], Awaitable[FunctionResult]],
) -> FunctionPlugin:
class WrappedFunction(FunctionPlugin):
pass
instance = WrappedFunction.__new__(WrappedFunction)
instance.function_id = function_id
instance.corpus_entry_cid = corpus_entry_cid
instance.capability_mask = capability_mask
instance.trigger_events = trigger_events or []
instance.accord_template = accord_template
instance.gsap_required = gsap_required
instance.chronicle_enabled = chronicle_enabled
instance.max_duration_seconds = max_duration_seconds
instance.display_name = display_name or function_id
instance.description = description
instance.version = version
async def handle(event: dict[str, Any], context: FunctionContext) -> FunctionResult:
return await fn(event, context)
instance.handle = handle # type: ignore[assignment]
instance.health_check = lambda: True # type: ignore[assignment]
# Auto-register if a registry is provided
if registry is not None:
registry.register(instance)
# Preserve original function metadata
functools.update_wrapper(instance, fn)
return instance
return decorator