Approval Workflow Engine (AWE)
A generic, configurable multi-stage approval workflow engine for OpenG2P. Caller services post artifacts for approval; AWE resolves stages and approvers, tracks decisions, and notifies callers via sig
Overview
The Approval Workflow Engine (AWE) is the platform-level service that governs multi-stage approvals for OpenG2P modules β change requests in the Registry, disbursements in PBMS, and any other artifact that must pass through configurable, multi-stage sign-off before taking effect.
It is not a BPMN engine or a workflow orchestrator for arbitrary business processes. It does exactly one thing well: resolve a chain of approvers, gate a caller-owned artifact on their decisions, and signal the caller when the outcome is known.
Built with FastAPI + async SQLAlchemy + PostgreSQL. Designed for horizontal scaling on Kubernetes, with one AWE deployment per caller module (registry-awe, pbms-awe, β¦) for clean isolation.
Looking for "what scenarios can I model?" β see the Scenarios catalog for a one-page index of every approval pattern AWE handles, mapped to the configuration knob that produces it.
Key capabilities
Caller-agnostic β AWE doesn't know your artifact's shape. Callers pass
(artifact_type, artifact_id, context)plus a callback URL; AWE only stores the identifier and a context snapshot used for approver resolution.Versioned policies β every edit to an active policy creates a new draft version. In-flight requests stay pinned to the version they started with, so policy changes never rewrite mid-flight approvals.
Flexible approver resolution β five rule types per stage: literal
user, Keycloakrole, Keycloakgroup,expression(JSONLogic over the request context), andhttp(escape hatch calling the caller's resolver endpoint). Rules union within a stage.Multiple decision modes β
all,any-N,quorum:N,percentage:P. Skip rules (skip_ifJSONLogic,on_empty) handle conditional bypass and zero-approver stages.Push notification via signed webhooks β state changes are POSTed to the caller with an HMAC signature and timestamp; retries with exponential backoff (1m β 5m β 15m β 1h β 6h) over ~24 hours before giving up.
Idempotent request creation β
Idempotency-Keyheader dedups retriedPOST /v1/awe/requestscalls so a caller's retry policy never creates duplicate approval flows.Immutable audit log β every state transition emits an
approval_eventrow; the API exposes a full timeline per request.Keycloak-native β inbound bearer tokens verified against JWKS; approver/group lookups use the Keycloak admin API.
Design at a glance
Caller UI never talks to AWE directly β the caller service proxies /v1/awe/tasks and decision calls on behalf of its end users. That keeps auth and CORS simple and lets the caller enrich the approver inbox with its own artifact detail.
Example β two-stage approval, happy path
The policy (registry.cr.v1) has two stages:
Stage 1 β "District officers" β mode
any-N:1, rulegroup: /districts/D1β resolves to Alice and Bob. Any one approval advances the stage.Stage 2 β "State directors" β mode
all, ruleuser: director-Xβ resolves to Director-X only. Director-X's approval finalizes the request.
The scenario:
A change request
cr-42(for district D1) is created in Registry somehow β by a field agent, a bulk import, an upstream system, a CSV upload; how and by whom is outside AWE's concern. Registry owns the CR.Registry posts an approval request to AWE.
Alice (a district officer) logs into Registry, sees
cr-42in her approval inbox, and approves β satisfying stage 1'sany-N:1, which skips Bob's task.Director-X logs into Registry, sees
cr-42in his approval inbox, and approves β satisfying stage 2'salland finalizing the request.AWE fires the final webhook, Registry applies the CR.
Alice and Director-X are purely approvers β they neither authored cr-42 nor edit it; they only review and approve what's already there.
Key point about the arrows: approvers never talk to AWE directly β they interact with the Registry UI, and Registry proxies /v1/awe/tasks + decision calls to AWE on their behalf. Every approver action below is drawn as two hops: Alice β Registry β AWE, response back the same way.
Example β request is cancelled
Detailed documentation
Policy model, stage modes, approver rule types, context semantics, skip rules, request lifecycle state machine, webhook contract (signature, retry schedule), PII / security posture, FAQ
REST API endpoints rendered live from OpenAPI 3.1 β request/response shapes, status codes, error-code catalog
Why this design over alternatives (Camunda, polling, multi-tenant), scalability model, delivery guarantees, engine state machine, approver-resolution caching
Local dev with Docker Compose, Helm chart install, configuration reference, Keycloak prerequisites, operational runbook, security considerations
Pytest smoke tests (hermetic, in-memory SQLite), test strategy, sample payloads
Versions
Source code
GitHub: https://github.com/OpenG2P/awe
Technology stack
Language
Python 3.11+
PSF License (permissive)
Web Framework
FastAPI
MIT
ASGI Server
Uvicorn
BSD-3-Clause
DB Driver
asyncpg
Apache 2.0
ORM
SQLAlchemy 2.x (async)
MIT
Config
Pydantic Settings
MIT
Auth
Keycloak OIDC (JWT/JWKS)
Apache 2.0
Rule engine
JSONLogic
MIT
Admin UI
React + Vite + TS
MIT
Database
PostgreSQL
PostgreSQL License (permissive)
Deployment
Kubernetes + Helm
Apache 2.0
All components use permissive open-source licenses. No copyleft (GPL) dependencies.
Last updated
Was this helpful?