Skip to content

Commit 995c086

Browse files
authored
Merge pull request #4 from PredicateSystems/phase4
local IdP
2 parents 65f8f0a + 347f224 commit 995c086

13 files changed

Lines changed: 2460 additions & 31 deletions

README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,93 @@ predicate-authority revoke intent --host 127.0.0.1 --port 8787 --hash <intent_ha
150150
predicate-authorityd --host 127.0.0.1 --port 8787 --mode local_only --policy-file examples/authorityd/policy.json
151151
```
152152

153+
### Identity mode options (`predicate-authorityd`)
154+
155+
- `--identity-mode local`: deterministic local bridge (default).
156+
- `--identity-mode local-idp`: local IdP-style signed token mode for dev/air-gapped workflows.
157+
- `--identity-mode oidc`: enterprise OIDC bridge mode.
158+
- `--identity-mode entra`: Microsoft Entra bridge mode.
159+
160+
Example (`local-idp`):
161+
162+
```bash
163+
export LOCAL_IDP_SIGNING_KEY="replace-with-strong-secret"
164+
predicate-authorityd \
165+
--host 127.0.0.1 \
166+
--port 8787 \
167+
--mode local_only \
168+
--policy-file examples/authorityd/policy.json \
169+
--identity-mode local-idp \
170+
--local-idp-issuer "http://localhost/predicate-local-idp" \
171+
--local-idp-audience "api://predicate-authority"
172+
```
173+
174+
### Local identity registry (ephemeral + TTL + flush queue)
175+
176+
Enable sidecar-managed local task identities and local ledger queue:
177+
178+
```bash
179+
PYTHONPATH=. predicate-authorityd \
180+
--host 127.0.0.1 \
181+
--port 8787 \
182+
--mode local_only \
183+
--policy-file examples/authorityd/policy.json \
184+
--identity-mode local-idp \
185+
--local-identity-enabled \
186+
--local-identity-registry-file ./.predicate-authorityd/local-identities.json \
187+
--local-identity-default-ttl-s 900 \
188+
--flush-worker-enabled \
189+
--flush-worker-interval-s 2.0 \
190+
--flush-worker-max-batch-size 50 \
191+
--flush-worker-dead-letter-max-attempts 5
192+
```
193+
194+
Runtime endpoints:
195+
196+
- `POST /identity/task` (issue ephemeral task identity)
197+
- `GET /identity/list` (list identities)
198+
- `POST /identity/revoke` (revoke identity)
199+
- `GET /ledger/flush-queue` (inspect pending local ledger queue)
200+
- `GET /ledger/dead-letter` (list quarantined queue items only)
201+
- `POST /ledger/flush-ack` (mark queue item as flushed)
202+
- `POST /ledger/flush-now` (manually trigger immediate queue flush)
203+
- `POST /ledger/requeue` (requeue quarantined item for retry)
204+
205+
Background flush worker status fields:
206+
207+
- `flush_cycle_count`
208+
- `flush_sent_count`
209+
- `flush_failed_count`
210+
- `flush_quarantined_count`
211+
- `last_flush_epoch_s`
212+
- `last_flush_error`
213+
214+
### How to run with control-plane shipping (out-of-the-box)
215+
216+
```bash
217+
export CONTROL_PLANE_URL="http://127.0.0.1:8080"
218+
export CONTROL_PLANE_TENANT_ID="dev-tenant"
219+
export CONTROL_PLANE_PROJECT_ID="dev-project"
220+
export CONTROL_PLANE_AUTH_TOKEN="<bearer-token>"
221+
222+
PYTHONPATH=. predicate-authorityd \
223+
--host 127.0.0.1 \
224+
--port 8787 \
225+
--mode local_only \
226+
--policy-file examples/authorityd/policy.json \
227+
--control-plane-enabled \
228+
--control-plane-fail-open
229+
```
230+
231+
The `/status` endpoint now includes:
232+
233+
- `control_plane_emitter_attached`
234+
- `control_plane_audit_push_success_count`
235+
- `control_plane_audit_push_failure_count`
236+
- `control_plane_usage_push_success_count`
237+
- `control_plane_usage_push_failure_count`
238+
- `control_plane_last_push_error`
239+
153240
## Security: Local Kill-Switch Path
154241

155242
`predicate-authority` supports fail-closed checks, local proof emission, and sidecar-managed revocation/token lifecycle for long-running agents.

docs/authorityd-operations.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# `predicate-authorityd` Operations Guide
2+
3+
This guide shows how to run the local sidecar daemon, provide a policy file, and verify health/status endpoints.
4+
5+
## 1) Sample `policy.json`
6+
7+
Create `examples/authorityd/policy.json`:
8+
9+
```json
10+
{
11+
"rules": [
12+
{
13+
"name": "allow-orders-http-post",
14+
"effect": "allow",
15+
"principals": ["agent:orders-*"],
16+
"actions": ["http.post"],
17+
"resources": ["https://api.vendor.com/orders"],
18+
"required_labels": []
19+
},
20+
{
21+
"name": "deny-admin-delete",
22+
"effect": "deny",
23+
"principals": ["agent:*"],
24+
"actions": ["http.delete"],
25+
"resources": ["https://api.vendor.com/admin/*"],
26+
"required_labels": []
27+
}
28+
]
29+
}
30+
```
31+
32+
## 2) Start the daemon
33+
34+
Run from repo root:
35+
36+
```bash
37+
PYTHONPATH=. predicate-authorityd \
38+
--host 127.0.0.1 \
39+
--port 8787 \
40+
--mode local_only \
41+
--policy-file examples/authorityd/policy.json \
42+
--policy-poll-interval-s 2.0 \
43+
--credential-store-file ./.predicate-authorityd/credentials.json
44+
```
45+
46+
### Optional: enable control-plane shipping
47+
48+
To automatically ship proof events and usage records to
49+
`predicate-authority-control-plane`, set:
50+
51+
```bash
52+
export CONTROL_PLANE_URL="http://127.0.0.1:8080"
53+
export CONTROL_PLANE_TENANT_ID="dev-tenant"
54+
export CONTROL_PLANE_PROJECT_ID="dev-project"
55+
export CONTROL_PLANE_AUTH_TOKEN="<bearer-token>"
56+
57+
PYTHONPATH=. predicate-authorityd \
58+
--host 127.0.0.1 \
59+
--port 8787 \
60+
--mode local_only \
61+
--policy-file examples/authorityd/policy.json \
62+
--control-plane-enabled \
63+
--control-plane-fail-open
64+
```
65+
66+
When enabled, daemon bootstrap auto-attaches `ControlPlaneTraceEmitter` so each
67+
authority decision pushes:
68+
69+
- audit events -> `/v1/audit/events:batch`
70+
- usage credits -> `/v1/metering/usage:batch`
71+
72+
## 3b) Optional local identity registry (ephemeral task identities)
73+
74+
Enable local identity support:
75+
76+
```bash
77+
PYTHONPATH=. predicate-authorityd \
78+
--host 127.0.0.1 \
79+
--port 8787 \
80+
--mode local_only \
81+
--policy-file examples/authorityd/policy.json \
82+
--identity-mode local-idp \
83+
--local-identity-enabled \
84+
--local-identity-registry-file ./.predicate-authorityd/local-identities.json \
85+
--local-identity-default-ttl-s 900 \
86+
--flush-worker-enabled \
87+
--flush-worker-interval-s 2.0 \
88+
--flush-worker-max-batch-size 50 \
89+
--flush-worker-dead-letter-max-attempts 5
90+
```
91+
92+
Issue an ephemeral identity:
93+
94+
```bash
95+
curl -s -X POST http://127.0.0.1:8787/identity/task \
96+
-H "Content-Type: application/json" \
97+
-d '{"principal_id":"agent:backend","task_id":"refactor-pr-102","ttl_seconds":120}'
98+
```
99+
100+
Inspect pending local ledger flush queue:
101+
102+
```bash
103+
curl -s http://127.0.0.1:8787/ledger/flush-queue | jq
104+
```
105+
106+
List quarantined dead-letter items only:
107+
108+
```bash
109+
curl -s http://127.0.0.1:8787/ledger/dead-letter | jq
110+
```
111+
112+
Manually trigger an immediate flush cycle:
113+
114+
```bash
115+
curl -s -X POST http://127.0.0.1:8787/ledger/flush-now \
116+
-H "Content-Type: application/json" \
117+
-d '{"max_items":50}' | jq
118+
```
119+
120+
Requeue a quarantined item for retry:
121+
122+
```bash
123+
curl -s -X POST http://127.0.0.1:8787/ledger/requeue \
124+
-H "Content-Type: application/json" \
125+
-d '{"queue_item_id":"q_abc123"}' | jq
126+
```
127+
128+
Flush worker behavior:
129+
130+
- reuses control-plane client retry policy (`--control-plane-max-retries`, `--control-plane-backoff-initial-s`),
131+
- drains up to `--flush-worker-max-batch-size` queue items per cycle,
132+
- quarantines entries after `--flush-worker-dead-letter-max-attempts` failed sends,
133+
- sleeps `--flush-worker-interval-s` between flush cycles.
134+
135+
Expected startup output:
136+
137+
```text
138+
predicate-authorityd listening on http://127.0.0.1:8787 (mode=local_only)
139+
```
140+
141+
## 3) Endpoint checks
142+
143+
### Health
144+
145+
```bash
146+
curl -s http://127.0.0.1:8787/health | jq
147+
```
148+
149+
Example response:
150+
151+
```json
152+
{
153+
"status": "ok",
154+
"mode": "local_only",
155+
"uptime_s": 12
156+
}
157+
```
158+
159+
### Status
160+
161+
```bash
162+
curl -s http://127.0.0.1:8787/status | jq
163+
```
164+
165+
Example response:
166+
167+
```json
168+
{
169+
"mode": "local_only",
170+
"policy_hot_reload_enabled": true,
171+
"revoked_principal_count": 0,
172+
"revoked_intent_count": 0,
173+
"revoked_mandate_count": 0,
174+
"proof_event_count": 0,
175+
"daemon_running": true,
176+
"policy_reload_count": 1,
177+
"policy_poll_error_count": 0,
178+
"last_policy_reload_epoch_s": 1700000000.0,
179+
"last_policy_poll_error": null
180+
}
181+
```
182+
183+
## 4) Verify policy hot-reload
184+
185+
1. Update `examples/authorityd/policy.json`.
186+
2. Wait for at most `--policy-poll-interval-s`.
187+
3. Check `/status` and confirm `policy_reload_count` increases.
188+
189+
## 5) Stop daemon
190+
191+
Press `Ctrl+C` in the daemon terminal.

predicate_authority/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ Core pieces:
88
- `ActionGuard` for pre-action `authorize` / `enforce`,
99
- `LocalMandateSigner` for signed short-lived mandates,
1010
- `InMemoryProofLedger` and optional `OpenTelemetryTraceEmitter`,
11-
- typed integration adapters (including `sdk-python` mapping helpers).
11+
- typed integration adapters (including `sdk-python` mapping helpers),
12+
- control-plane client primitives for shipping proof and usage batches to hosted APIs,
13+
- local identity registry primitives (ephemeral task identities + local flush queue).

predicate_authority/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,30 @@
33
EntraIdentityBridge,
44
IdentityBridge,
55
IdentityProviderType,
6+
LocalIdPBridge,
7+
LocalIdPBridgeConfig,
68
OIDCBridgeConfig,
79
OIDCIdentityBridge,
810
TokenExchangeResult,
911
)
12+
from predicate_authority.control_plane import (
13+
AuditEventEnvelope,
14+
ControlPlaneClient,
15+
ControlPlaneClientConfig,
16+
ControlPlaneTraceEmitter,
17+
UsageCreditRecord,
18+
)
1019
from predicate_authority.daemon import DaemonConfig, PredicateAuthorityDaemon
1120
from predicate_authority.errors import AuthorizationDeniedError
1221
from predicate_authority.guard import ActionExecutionResult, ActionGuard
22+
from predicate_authority.local_identity import (
23+
CompositeTraceEmitter,
24+
LedgerQueueItem,
25+
LocalIdentityRegistry,
26+
LocalIdentityRegistryStats,
27+
LocalLedgerQueueEmitter,
28+
TaskIdentityRecord,
29+
)
1330
from predicate_authority.mandate import LocalMandateSigner
1431
from predicate_authority.policy import PolicyEngine, PolicyMatchResult
1532
from predicate_authority.policy_source import PolicyFileSource, PolicyReloadResult
@@ -30,14 +47,23 @@
3047
"ActionGuard",
3148
"AuthorityMode",
3249
"AuthorizationDeniedError",
50+
"AuditEventEnvelope",
51+
"ControlPlaneClient",
52+
"ControlPlaneClientConfig",
53+
"ControlPlaneTraceEmitter",
3354
"CredentialRecord",
3455
"DaemonConfig",
3556
"EntraBridgeConfig",
3657
"EntraIdentityBridge",
3758
"IdentityBridge",
3859
"IdentityProviderType",
3960
"InMemoryProofLedger",
61+
"LocalIdPBridge",
62+
"LocalIdPBridgeConfig",
4063
"LocalCredentialStore",
64+
"LocalIdentityRegistry",
65+
"LocalIdentityRegistryStats",
66+
"LocalLedgerQueueEmitter",
4167
"LocalMandateSigner",
4268
"LocalRevocationCache",
4369
"OIDCBridgeConfig",
@@ -53,4 +79,8 @@
5379
"SidecarError",
5480
"SidecarStatus",
5581
"TokenExchangeResult",
82+
"CompositeTraceEmitter",
83+
"LedgerQueueItem",
84+
"TaskIdentityRecord",
85+
"UsageCreditRecord",
5686
]

0 commit comments

Comments
 (0)