Declarative policy-as-code evaluator. JSON/YAML rules + Python predicates over arbitrary context objects. Enforces AI Procurement Decision Card conditions at request time. Optional audit-stream-py integration via AUDIT_STREAM_URL.
Project description
policy-as-code-engine
Declarative policy-as-code evaluator for Python services. JSON/YAML rules → first-match-wins evaluation → structured allow/deny decision with the matching rule and the reason. Cheap to embed; ships with a FastAPI surface; pairs directly with procurement-decision-api so the same Decision Card that records a buyer's posture also becomes the runtime gate that enforces it.
Why
Most policy engines either ask you to learn a DSL (Rego, Cedar) or hand you a dictionary-of-lambdas and call it a library. Neither is the right shape when the source of truth is a JSON document a human signed off on. This engine:
- Reads JSON/YAML bundles. No DSL. The matcher tree is the policy.
- Returns why, not just what. Every decision carries the matched policy + rule + reason. Operators get a real audit log on each evaluation.
- Bridges to the Kinetic Gain Protocol Suite. A single endpoint turns an AI Procurement Decision Card into a runtime-enforceable
PolicyBundle— approve, reject, or approve-with-conditions all map to concrete allow/deny logic.
Install
pip install policy-as-code-engine
# with the FastAPI surface:
pip install "policy-as-code-engine[api]"
Python 3.11+. Runtime deps: pydantic + PyYAML.
Library quickstart
from policy_as_code_engine import (
EvaluationContext,
PolicyBundle,
PolicyEvaluator,
)
bundle = PolicyBundle.model_validate({
"bundle_id": "edu-gate",
"policies": [{
"id": "writes-require-admin",
"default_effect": "deny",
"rules": [
{
"id": "admin-writes",
"effect": "allow",
"when": {
"kind": "all_of",
"matchers": [
{"kind": "in", "field": "action", "value": ["create", "update", "delete"]},
{"kind": "eq", "field": "subject.role", "value": "admin"},
],
},
},
],
}],
})
ctx = EvaluationContext(
subject={"id": "u-42", "role": "admin"},
action="update",
resource={"id": "doc-7"},
)
result = PolicyEvaluator().evaluate(bundle, ctx)
print(result.decision.kind) # "allow"
print(result.decision.matched_rule_id) # "admin-writes"
print(result.decision.reason) # "matched rule 'admin-writes'"
result.policy_decisions carries every per-policy outcome — drop it straight into your audit log.
Bundle DSL
A bundle is a small recursive structure. Matchers compose; rules ordered.
Field matchers
| Kind | Notes |
|---|---|
eq / ne |
Strict equality. |
gt / gte / lt / lte |
Comparison; returns false on incompatible types (won't raise). |
in / not_in |
value must be a list. |
contains |
Works against strings, lists, sets, dicts. |
exists / missing |
No value. Operates against the dotted-path resolver. |
regex |
Compiled patterns are cached per-evaluator. |
starts_with / ends_with |
String-only. |
Composite matchers
| Kind | Children | Truth |
|---|---|---|
all_of |
matchers: [...] |
All children true. |
any_of |
matchers: [...] |
At least one child true. |
not |
matcher: {...} |
Inverts the child. |
always |
— | Always true. Useful as a final catch-all. |
Dotted paths
The resolver looks at the merged context (data + subject + action + resource):
subject.role
resource.tags.0 # list index
data.conditions_satisfied.dpa-signed
Missing segments produce a _MISSING sentinel — exists / missing matchers see it; every other matcher returns false.
FastAPI surface
pip install "policy-as-code-engine[api]"
python -m policy_as_code_engine # binds 0.0.0.0:8089 by default
| Method | Path | What it does |
|---|---|---|
| GET | /healthz |
Liveness probe. |
| GET | / |
Service info. |
| POST | /bundles |
Register a PolicyBundle in memory. |
| GET | /bundles |
List registered bundle IDs. |
| GET | /bundles/{bundle_id} |
Inspect a registered bundle. |
| POST | /bundles/{bundle_id}/evaluate |
Evaluate a stored bundle against an EvaluationContext. |
| POST | /evaluate |
One-shot. Bundle + context in, decision out. |
| POST | /bundles/from-decision-card |
The cross-ecosystem hook. Turn a Kinetic Gain Procurement Decision Card into a PolicyBundle and register it. |
The cross-ecosystem hook
The headline feature. An AI Procurement Decision Card is the buyer-side record that says "we evaluated this vendor and our position is X." This engine turns that human-authored artifact into a runtime gate, mechanically.
curl -X POST http://localhost:8089/bundles/from-decision-card \
-H 'Content-Type: application/json' \
-d @decision-card.json
Mapping:
| Decision Card status | Resulting bundle |
|---|---|
approved |
Single allow-all policy. |
rejected · rejected-with-remediation · withdrawn · expired · pending |
Single deny-all policy (fail safe). |
approved-with-conditions |
One policy per condition. Each policy allows only when conditions_satisfied.{condition_id} is true in the evaluation context; deny otherwise. The bundle combiner does deny-trumps-allow, so every condition must be satisfied to allow. |
Wire your own satisfaction signal — DPA verifier, bias-audit freshness check, attestation timestamp — into the context, and the bundle does the rest.
from policy_as_code_engine import (
EvaluationContext,
PolicyEvaluator,
policy_bundle_from_decision_card,
)
card = {...} # POST /decisions/draft output from procurement-decision-api
bundle = policy_bundle_from_decision_card(card)
ctx = EvaluationContext(
subject={"id": "u-1"},
action="enroll",
data={
"conditions_satisfied": {
"dpa-signed": True,
"bias-audit-fresh": True,
}
},
)
decision = PolicyEvaluator().evaluate(bundle, ctx).decision
CLI
python -m policy_as_code_engine eval examples/example-bundle.yaml examples/example-context.json
Prints the full EvaluationResult as JSON. Exits non-zero on deny.
How decisions combine
Inside a single policy: first matching rule wins, otherwise default_effect.
Across the bundle:
deny -> deny (any policy denies => bundle denies)
allow -> allow (otherwise, any allow => bundle allows)
neither -> not_applicable
Per-policy decisions are always returned — useful for "we denied because of policy B, but A would have allowed" audit narratives.
Tests
pip install -e ".[dev]"
ruff check src tests && ruff format --check src tests
mypy src
pytest -v
CI matrix runs Python 3.11 / 3.12 / 3.13.
Related in this ecosystem
- procurement-decision-api — drafts the Decision Cards that this engine enforces.
- ai-procurement-decision-spec — the v0.1 schema.
- slo-budget-tracker — error-budget tracker that you can wire into the same FastAPI app.
- reliability-toolkit-rs — Rust async reliability primitives.
- More at kineticgain.com.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file policy_as_code_engine-0.1.1.tar.gz.
File metadata
- Download URL: policy_as_code_engine-0.1.1.tar.gz
- Upload date:
- Size: 22.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c1a84f7f954371dd1c12f2864a12873ec70ca4459916f91e519239c7c805dc2
|
|
| MD5 |
f0006a4ce558ba30c3847e0408a7ffa2
|
|
| BLAKE2b-256 |
0889d20a05177c14916195c28c61bf2f7922bd1ace259433064dcb1810c29ede
|
Provenance
The following attestation bundles were made for policy_as_code_engine-0.1.1.tar.gz:
Publisher:
publish.yml on mizcausevic-dev/policy-as-code-engine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
policy_as_code_engine-0.1.1.tar.gz -
Subject digest:
1c1a84f7f954371dd1c12f2864a12873ec70ca4459916f91e519239c7c805dc2 - Sigstore transparency entry: 1549214785
- Sigstore integration time:
-
Permalink:
mizcausevic-dev/policy-as-code-engine@86387fd7948c12010d7335e7994e090c253dcc6c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mizcausevic-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86387fd7948c12010d7335e7994e090c253dcc6c -
Trigger Event:
push
-
Statement type:
File details
Details for the file policy_as_code_engine-0.1.1-py3-none-any.whl.
File metadata
- Download URL: policy_as_code_engine-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb3355fc7e2feb87a934a713f1d02af0616c4f87e5f81eaaebbfdbb7e0740b3a
|
|
| MD5 |
6990515e80dbd8835789bb8d49fbdaf2
|
|
| BLAKE2b-256 |
9e280c77334e4e08fc5b7b9341d5f7836ac927806cfdaa88693da80201165fc1
|
Provenance
The following attestation bundles were made for policy_as_code_engine-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on mizcausevic-dev/policy-as-code-engine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
policy_as_code_engine-0.1.1-py3-none-any.whl -
Subject digest:
cb3355fc7e2feb87a934a713f1d02af0616c4f87e5f81eaaebbfdbb7e0740b3a - Sigstore transparency entry: 1549214857
- Sigstore integration time:
-
Permalink:
mizcausevic-dev/policy-as-code-engine@86387fd7948c12010d7335e7994e090c253dcc6c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mizcausevic-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86387fd7948c12010d7335e7994e090c253dcc6c -
Trigger Event:
push
-
Statement type: