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

62 lines
2 KiB
Python

"""BillingProcessor — governed billing drain function per §5."""
from __future__ import annotations
from typing import Any
from gsap_broker.functions.base import FunctionContext, FunctionPlugin, FunctionResult
RATE_CARD: dict[str, float] = {
"connector_invocation": 0.002,
"function_invocation": 0.001,
"chronicle_event": 0.0005,
"gsap_ac_issued": 0.003,
"gsap_cr_completed": 0.005,
}
class BillingProcessor(FunctionPlugin):
function_id = "billing-processor"
corpus_entry_cid = "sha256:" + "b" * 64
capability_mask = 3 # MUTATE
trigger_events = ["GSAP_CR_RECEIVED"]
accord_template = ""
gsap_required = False
chronicle_enabled = True
max_duration_seconds = 10
display_name = "Billing Processor"
description = "Governed billing drain — only bills completed outcomes with Chronicle CID reference."
version = "0.1.0"
async def handle(self, event: dict[str, Any], context: FunctionContext) -> FunctionResult:
outcome = event.get("outcome", "")
if outcome != "completed":
return FunctionResult(
success=True,
data={"billed": False, "reason": f"Outcome '{outcome}' is not billable"},
metadata={"function_id": self.function_id},
)
event_type = event.get("event_type", "gsap_cr_completed")
rate = RATE_CARD.get(event_type, RATE_CARD["gsap_cr_completed"])
quantity = event.get("quantity", 1)
amount = rate * quantity
billing_record = {
"billed": True,
"event_type": event_type,
"rate": rate,
"quantity": quantity,
"amount": amount,
"chronicle_cid": context.trigger_event_cid or event.get("chronicle_cid", ""),
"invocation_id": context.invocation_id,
}
return FunctionResult(
success=True,
data=billing_record,
metadata={"function_id": self.function_id},
)
def health_check(self) -> bool:
return True