Skip to content

Commit 62c57e6

Browse files
authored
Merge pull request #1 from PredicateSystems/phase1
Phase 1: SDK only guard
2 parents 95d6b05 + d013ed4 commit 62c57e6

36 files changed

Lines changed: 1682 additions & 2 deletions
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: phase1-ci-and-release
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: ["main", "phase1"]
7+
workflow_dispatch:
8+
inputs:
9+
publish:
10+
description: "Run ordered publish jobs"
11+
required: true
12+
default: "false"
13+
type: choice
14+
options: ["false", "true"]
15+
16+
jobs:
17+
quality:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.11"
27+
28+
- name: Install dependencies
29+
run: python -m pip install --upgrade pip pre-commit pytest
30+
31+
- name: Verify package release order
32+
run: python scripts/verify_release_order.py
33+
34+
- name: Run tests
35+
run: python -m pytest -q
36+
37+
- name: Run pre-commit checks
38+
run: pre-commit run --all-files
39+
40+
publish-predicate-contracts:
41+
runs-on: ubuntu-latest
42+
needs: [quality]
43+
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'true'
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
48+
- name: Set up Python
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: "3.11"
52+
53+
- name: Install build tooling
54+
run: python -m pip install --upgrade pip build twine
55+
56+
- name: Verify release order
57+
run: python scripts/verify_release_order.py
58+
59+
- name: Build predicate-contracts
60+
run: python -m build predicate_contracts
61+
62+
- name: Validate distribution metadata
63+
run: twine check predicate_contracts/dist/*
64+
65+
- name: Publish predicate-contracts to PyPI
66+
env:
67+
TWINE_USERNAME: __token__
68+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN_PREDICATE_CONTRACTS }}
69+
run: twine upload predicate_contracts/dist/*
70+
71+
publish-predicate-authority:
72+
runs-on: ubuntu-latest
73+
needs: [publish-predicate-contracts]
74+
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'true'
75+
steps:
76+
- name: Checkout
77+
uses: actions/checkout@v4
78+
79+
- name: Set up Python
80+
uses: actions/setup-python@v5
81+
with:
82+
python-version: "3.11"
83+
84+
- name: Install build tooling
85+
run: python -m pip install --upgrade pip build twine
86+
87+
- name: Verify release order
88+
run: python scripts/verify_release_order.py
89+
90+
- name: Build predicate-authority
91+
run: python -m build predicate_authority
92+
93+
- name: Validate distribution metadata
94+
run: twine check predicate_authority/dist/*
95+
96+
- name: Publish predicate-authority to PyPI
97+
env:
98+
TWINE_USERNAME: __token__
99+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN_PREDICATE_AUTHORITY }}
100+
run: twine upload predicate_authority/dist/*

.github/workflows/tests.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: tests
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: ["main", "phase1"]
7+
workflow_dispatch:
8+
9+
jobs:
10+
pytest:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
python-version: ["3.11", "3.12"]
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
26+
- name: Install test dependencies
27+
run: python -m pip install --upgrade pip pytest
28+
29+
- name: Run tests
30+
run: python -m pytest -q

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ traces/
5555
artifacts/
5656
tmp/
5757
temp/
58+
59+
# Local build artifacts from package-level builds
60+
predicate_authority/predicate_authority/
61+
predicate_contracts/predicate_contracts/

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Pre-commit hooks for AgentIdentity repository
1+
# Pre-commit hooks for predicate-authority repository
22
# Baseline adapted from /Code/Sentience/sdk-python/.pre-commit-config.yaml
33

44
repos:

Makefile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
.PHONY: hooks lint format format-python format-docs lint-docs
1+
.PHONY: hooks lint test examples verify-release-order build-packages format format-python format-docs lint-docs
22

33
hooks:
44
pre-commit install
55

66
lint:
77
pre-commit run --all-files
88

9+
test:
10+
python -m pytest -q
11+
12+
examples:
13+
PYTHONPATH=. python examples/browser_guard_example.py
14+
PYTHONPATH=. python examples/mcp_tool_guard_example.py
15+
PYTHONPATH=. python examples/outbound_http_guard_example.py
16+
17+
verify-release-order:
18+
python scripts/verify_release_order.py
19+
20+
build-packages:
21+
python -m build predicate_contracts
22+
python -m build predicate_authority
23+
924
format: format-python format-docs
1025

1126
format-python:

README.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Predicate Authority
2+
3+
**Deterministic Authority for AI Agents: Secure the "Confused Deputy" with your existing Identity stack.**
4+
5+
[![License](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue.svg)](LICENSE)
6+
[![PyPI - predicate-authority](https://img.shields.io/pypi/v/predicate-authority.svg)](https://pypi.org/project/predicate-authority/)
7+
[![PyPI - predicate-contracts](https://img.shields.io/pypi/v/predicate-contracts.svg)](https://pypi.org/project/predicate-contracts/)
8+
9+
`predicate-authority` is a production-grade pre-execution authority layer that binds AI agent identity to deterministic state. It bridges standard IdPs (Entra ID, Okta, OIDC) with runtime verification so every sensitive action is authorized, bounded, and provable.
10+
11+
## Why Predicate Authority?
12+
13+
Most agent security fails because it relies on static API keys or broad permissions. Predicate introduces short-lived mandates that are cryptographically tied to:
14+
15+
- `state_hash` (what state the agent is in),
16+
- `intent_hash` (what action it intends to perform),
17+
- policy constraints and required verification labels.
18+
19+
This closes the confused-deputy gap where an agent can misuse delegated credentials.
20+
21+
- **Bridge, don't replace**: leverage existing enterprise identity and governance.
22+
- **Fail-closed by design**: deny before execution when state/intent/policy checks fail.
23+
- **Deterministic binding**: authority is tied to runtime evidence, not only identity.
24+
- **Provable controls**: each decision can emit signed proof events for audit pipelines.
25+
26+
### Why not just use IdP directly?
27+
28+
You should still use Entra/Okta/OIDC for identity and token issuance. `predicate-authority` adds the runtime control layer those systems do not provide by default for AI agents:
29+
30+
- pre-execution allow/deny checks right before each sensitive action,
31+
- binding authority to current `state_hash` and `intent_hash`,
32+
- optional required verification labels from runtime checks (currently web-agent only via [predicate-sdk](https://github.com/PredicateSystems/sdk-python) integration),
33+
- fail-closed local enforcement and per-decision proof events.
34+
35+
In practice: IdP answers **who the principal is**, while `predicate-authority` answers **whether this exact action is allowed right now in this state**.
36+
37+
## Repository Components
38+
39+
| Package | Purpose |
40+
| --- | --- |
41+
| `predicate_contracts` | Shared typed contracts and protocols (`ActionRequest`, `PolicyRule`, evidence, decision/proof models). |
42+
| `predicate_authority` | Runtime authorization engine (`PolicyEngine`, `ActionGuard`, mandate signing, proof ledger, telemetry emitter). |
43+
| `examples/` | Browser/MCP/HTTP guard examples using the local Phase 1 runtime. |
44+
45+
## Phase 1 Status
46+
47+
Implemented in this repository:
48+
49+
- local pre-execution `ActionGuard.authorize(...)` and `enforce(...)`,
50+
- signed local mandates with TTL (`LocalMandateSigner`),
51+
- policy evaluation with deny precedence and required verification labels,
52+
- typed [predicate-sdk](https://github.com/PredicateSystems/sdk-python) integration adapter (`predicate_authority.integrations`),
53+
- OpenTelemetry-compatible trace emitter (`OpenTelemetryTraceEmitter`),
54+
- pytest coverage for core authorization, mandate, integration, and telemetry flows.
55+
56+
Planned in upcoming phases:
57+
58+
- `predicate-authorityd` sidecar for token lifecycle and local kill-switch,
59+
- enterprise IdP bridge hardening (Entra/Okta/OIDC adapters),
60+
- hosted governance control plane.
61+
62+
## Installation
63+
64+
```bash
65+
pip install predicate-authority
66+
```
67+
68+
For shared contracts directly:
69+
70+
```bash
71+
pip install predicate-contracts
72+
```
73+
74+
## Quick Start (Phase 1 API)
75+
76+
```python
77+
from predicate_authority import ActionGuard, InMemoryProofLedger, LocalMandateSigner, PolicyEngine
78+
from predicate_contracts import (
79+
ActionRequest,
80+
ActionSpec,
81+
PolicyEffect,
82+
PolicyRule,
83+
PrincipalRef,
84+
StateEvidence,
85+
VerificationEvidence,
86+
)
87+
88+
guard = ActionGuard(
89+
policy_engine=PolicyEngine(
90+
rules=(
91+
PolicyRule(
92+
name="allow-payment-submit",
93+
effect=PolicyEffect.ALLOW,
94+
principals=("agent:payments",),
95+
actions=("http.post",),
96+
resources=("https://finance.example.com/transfers",),
97+
),
98+
)
99+
),
100+
mandate_signer=LocalMandateSigner(secret_key="dev-secret"),
101+
proof_ledger=InMemoryProofLedger(),
102+
)
103+
104+
request = ActionRequest(
105+
principal=PrincipalRef(principal_id="agent:payments"),
106+
action_spec=ActionSpec(
107+
action="http.post",
108+
resource="https://finance.example.com/transfers",
109+
intent="submit transfer request #1234",
110+
),
111+
state_evidence=StateEvidence(source="backend", state_hash="state-hash-abc"),
112+
verification_evidence=VerificationEvidence(),
113+
)
114+
115+
decision = guard.authorize(request)
116+
if not decision.allowed:
117+
raise RuntimeError(f"Authority denied: {decision.reason.value}")
118+
```
119+
120+
See runnable examples in:
121+
122+
- `examples/browser_guard_example.py`
123+
- `examples/mcp_tool_guard_example.py`
124+
- `examples/outbound_http_guard_example.py`
125+
126+
## Security: Local Kill-Switch Path
127+
128+
The current Phase 1 runtime supports fail-closed checks and local proof emission. The sidecar model (`predicate-authorityd`) is planned to provide instant local revocation and managed token lifecycle for long-running production agents.
129+
130+
## Release
131+
132+
- CI workflow: `.github/workflows/phase1-ci-and-release.yml`
133+
- Release guide: `docs/pypi-release-guide.md`
134+
135+
Publish order is always:
136+
137+
1. `predicate-contracts`
138+
2. `predicate-authority`
139+
140+
## License
141+
142+
Dual-licensed under **MIT** and **Apache 2.0**:
143+
144+
- `LICENSE-MIT`
145+
- `LICENSE-APACHE`
146+
147+
---
148+
149+
Copyright (c) 2026 Predicate Systems Inc.

0 commit comments

Comments
 (0)