How StayFn works
Client / partner system OPERA Cloud (OHIP) │ X-Tenant-Api-Key ▲ REST (OAuth, x-app-key, x-hotelid) ▼ │ Streaming API (GraphQL over WebSocket)┌──────────────────────── StayFn.Host (one container) ─────────────────────────┐│ /api/functions/{area}/{name}/invoke ──► invocation pipeline ││ /api/events/webhook/{source} │ 1 tenant + API key scope ││ /api/admin/* (JWT) /ui /mcp │ 2 quotas ││ /health /metrics │ 3 registry lookup + JSON schema ││ │ 4 idempotency ││ Workers: stream ingest, outbox │ 5 invocation row ││ dispatch, scheduler, metering, │ 6 OHIP rate-limit budget ││ retention, stale-invocation reaper │ 7 your function (IFunctionContext) ││ │ 8 retries 9 completion 10 dead ││ │ letter │└───────────────────────────────────────┴──────────────────────────────────────┘ │ PostgreSQL 16 (row-level security per tenant)- A function is a class with
[OhipFunction("name")]and aRun(input, IFunctionContext ctx)method. It lives in an area (rsv,crm,custom, …), so its full name is<area>/<name>, for examplersv/get-reservation. The area comes fromArea =on the attribute, a[FunctionArea]attribute, or the last namespace segment when it names a known OHIP area. Otherwise it iscustom. - A handler is a method with
[EventHandler("reservation.changed")]. It runs as an ordinary invocation for each matching event. - Functions reach OHIP only through
ctx.Ohip, which adds the token,x-app-key,x-hotelidand correlation id, applies the rate-limit governor, and records every upstream call so the dashboard can show it. - Events from the OHIP Streaming API or from signed webhooks land in an inbox (deduplicated), then an outbox row per handler is dispatched in order per hotel and aggregate. This works across any number of replicas.
Design decisions are recorded in docs/adr/0001-architecture.md (decisions D1–D16). Verified OHIP facts, with evidence levels, are in
docs/adr/0002-ohip-facts.md.