Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,93 @@ predicate-authority revoke intent --host 127.0.0.1 --port 8787 --hash <intent_ha
predicate-authorityd --host 127.0.0.1 --port 8787 --mode local_only --policy-file examples/authorityd/policy.json
```

### Identity mode options (`predicate-authorityd`)

- `--identity-mode local`: deterministic local bridge (default).
- `--identity-mode local-idp`: local IdP-style signed token mode for dev/air-gapped workflows.
- `--identity-mode oidc`: enterprise OIDC bridge mode.
- `--identity-mode entra`: Microsoft Entra bridge mode.

Example (`local-idp`):

```bash
export LOCAL_IDP_SIGNING_KEY="replace-with-strong-secret"
predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--identity-mode local-idp \
--local-idp-issuer "http://localhost/predicate-local-idp" \
--local-idp-audience "api://predicate-authority"
```

### Local identity registry (ephemeral + TTL + flush queue)

Enable sidecar-managed local task identities and local ledger queue:

```bash
PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--identity-mode local-idp \
--local-identity-enabled \
--local-identity-registry-file ./.predicate-authorityd/local-identities.json \
--local-identity-default-ttl-s 900 \
--flush-worker-enabled \
--flush-worker-interval-s 2.0 \
--flush-worker-max-batch-size 50 \
--flush-worker-dead-letter-max-attempts 5
```

Runtime endpoints:

- `POST /identity/task` (issue ephemeral task identity)
- `GET /identity/list` (list identities)
- `POST /identity/revoke` (revoke identity)
- `GET /ledger/flush-queue` (inspect pending local ledger queue)
- `GET /ledger/dead-letter` (list quarantined queue items only)
- `POST /ledger/flush-ack` (mark queue item as flushed)
- `POST /ledger/flush-now` (manually trigger immediate queue flush)
- `POST /ledger/requeue` (requeue quarantined item for retry)

Background flush worker status fields:

- `flush_cycle_count`
- `flush_sent_count`
- `flush_failed_count`
- `flush_quarantined_count`
- `last_flush_epoch_s`
- `last_flush_error`

### How to run with control-plane shipping (out-of-the-box)

```bash
export CONTROL_PLANE_URL="http://127.0.0.1:8080"
export CONTROL_PLANE_TENANT_ID="dev-tenant"
export CONTROL_PLANE_PROJECT_ID="dev-project"
export CONTROL_PLANE_AUTH_TOKEN="<bearer-token>"

PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--control-plane-enabled \
--control-plane-fail-open
```

The `/status` endpoint now includes:

- `control_plane_emitter_attached`
- `control_plane_audit_push_success_count`
- `control_plane_audit_push_failure_count`
- `control_plane_usage_push_success_count`
- `control_plane_usage_push_failure_count`
- `control_plane_last_push_error`

## Security: Local Kill-Switch Path

`predicate-authority` supports fail-closed checks, local proof emission, and sidecar-managed revocation/token lifecycle for long-running agents.
Expand Down
191 changes: 191 additions & 0 deletions docs/authorityd-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# `predicate-authorityd` Operations Guide

This guide shows how to run the local sidecar daemon, provide a policy file, and verify health/status endpoints.

## 1) Sample `policy.json`

Create `examples/authorityd/policy.json`:

```json
{
"rules": [
{
"name": "allow-orders-http-post",
"effect": "allow",
"principals": ["agent:orders-*"],
"actions": ["http.post"],
"resources": ["https://api.vendor.com/orders"],
"required_labels": []
},
{
"name": "deny-admin-delete",
"effect": "deny",
"principals": ["agent:*"],
"actions": ["http.delete"],
"resources": ["https://api.vendor.com/admin/*"],
"required_labels": []
}
]
}
```

## 2) Start the daemon

Run from repo root:

```bash
PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--policy-poll-interval-s 2.0 \
--credential-store-file ./.predicate-authorityd/credentials.json
```

### Optional: enable control-plane shipping

To automatically ship proof events and usage records to
`predicate-authority-control-plane`, set:

```bash
export CONTROL_PLANE_URL="http://127.0.0.1:8080"
export CONTROL_PLANE_TENANT_ID="dev-tenant"
export CONTROL_PLANE_PROJECT_ID="dev-project"
export CONTROL_PLANE_AUTH_TOKEN="<bearer-token>"

PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--control-plane-enabled \
--control-plane-fail-open
```

When enabled, daemon bootstrap auto-attaches `ControlPlaneTraceEmitter` so each
authority decision pushes:

- audit events -> `/v1/audit/events:batch`
- usage credits -> `/v1/metering/usage:batch`

## 3b) Optional local identity registry (ephemeral task identities)

Enable local identity support:

```bash
PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file examples/authorityd/policy.json \
--identity-mode local-idp \
--local-identity-enabled \
--local-identity-registry-file ./.predicate-authorityd/local-identities.json \
--local-identity-default-ttl-s 900 \
--flush-worker-enabled \
--flush-worker-interval-s 2.0 \
--flush-worker-max-batch-size 50 \
--flush-worker-dead-letter-max-attempts 5
```

Issue an ephemeral identity:

```bash
curl -s -X POST http://127.0.0.1:8787/identity/task \
-H "Content-Type: application/json" \
-d '{"principal_id":"agent:backend","task_id":"refactor-pr-102","ttl_seconds":120}'
```

Inspect pending local ledger flush queue:

```bash
curl -s http://127.0.0.1:8787/ledger/flush-queue | jq
```

List quarantined dead-letter items only:

```bash
curl -s http://127.0.0.1:8787/ledger/dead-letter | jq
```

Manually trigger an immediate flush cycle:

```bash
curl -s -X POST http://127.0.0.1:8787/ledger/flush-now \
-H "Content-Type: application/json" \
-d '{"max_items":50}' | jq
```

Requeue a quarantined item for retry:

```bash
curl -s -X POST http://127.0.0.1:8787/ledger/requeue \
-H "Content-Type: application/json" \
-d '{"queue_item_id":"q_abc123"}' | jq
```

Flush worker behavior:

- reuses control-plane client retry policy (`--control-plane-max-retries`, `--control-plane-backoff-initial-s`),
- drains up to `--flush-worker-max-batch-size` queue items per cycle,
- quarantines entries after `--flush-worker-dead-letter-max-attempts` failed sends,
- sleeps `--flush-worker-interval-s` between flush cycles.

Expected startup output:

```text
predicate-authorityd listening on http://127.0.0.1:8787 (mode=local_only)
```

## 3) Endpoint checks

### Health

```bash
curl -s http://127.0.0.1:8787/health | jq
```

Example response:

```json
{
"status": "ok",
"mode": "local_only",
"uptime_s": 12
}
```

### Status

```bash
curl -s http://127.0.0.1:8787/status | jq
```

Example response:

```json
{
"mode": "local_only",
"policy_hot_reload_enabled": true,
"revoked_principal_count": 0,
"revoked_intent_count": 0,
"revoked_mandate_count": 0,
"proof_event_count": 0,
"daemon_running": true,
"policy_reload_count": 1,
"policy_poll_error_count": 0,
"last_policy_reload_epoch_s": 1700000000.0,
"last_policy_poll_error": null
}
```

## 4) Verify policy hot-reload

1. Update `examples/authorityd/policy.json`.
2. Wait for at most `--policy-poll-interval-s`.
3. Check `/status` and confirm `policy_reload_count` increases.

## 5) Stop daemon

Press `Ctrl+C` in the daemon terminal.
4 changes: 3 additions & 1 deletion predicate_authority/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ Core pieces:
- `ActionGuard` for pre-action `authorize` / `enforce`,
- `LocalMandateSigner` for signed short-lived mandates,
- `InMemoryProofLedger` and optional `OpenTelemetryTraceEmitter`,
- typed integration adapters (including `sdk-python` mapping helpers).
- typed integration adapters (including `sdk-python` mapping helpers),
- control-plane client primitives for shipping proof and usage batches to hosted APIs,
- local identity registry primitives (ephemeral task identities + local flush queue).
30 changes: 30 additions & 0 deletions predicate_authority/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@
EntraIdentityBridge,
IdentityBridge,
IdentityProviderType,
LocalIdPBridge,
LocalIdPBridgeConfig,
OIDCBridgeConfig,
OIDCIdentityBridge,
TokenExchangeResult,
)
from predicate_authority.control_plane import (
AuditEventEnvelope,
ControlPlaneClient,
ControlPlaneClientConfig,
ControlPlaneTraceEmitter,
UsageCreditRecord,
)
from predicate_authority.daemon import DaemonConfig, PredicateAuthorityDaemon
from predicate_authority.errors import AuthorizationDeniedError
from predicate_authority.guard import ActionExecutionResult, ActionGuard
from predicate_authority.local_identity import (
CompositeTraceEmitter,
LedgerQueueItem,
LocalIdentityRegistry,
LocalIdentityRegistryStats,
LocalLedgerQueueEmitter,
TaskIdentityRecord,
)
from predicate_authority.mandate import LocalMandateSigner
from predicate_authority.policy import PolicyEngine, PolicyMatchResult
from predicate_authority.policy_source import PolicyFileSource, PolicyReloadResult
Expand All @@ -30,14 +47,23 @@
"ActionGuard",
"AuthorityMode",
"AuthorizationDeniedError",
"AuditEventEnvelope",
"ControlPlaneClient",
"ControlPlaneClientConfig",
"ControlPlaneTraceEmitter",
"CredentialRecord",
"DaemonConfig",
"EntraBridgeConfig",
"EntraIdentityBridge",
"IdentityBridge",
"IdentityProviderType",
"InMemoryProofLedger",
"LocalIdPBridge",
"LocalIdPBridgeConfig",
"LocalCredentialStore",
"LocalIdentityRegistry",
"LocalIdentityRegistryStats",
"LocalLedgerQueueEmitter",
"LocalMandateSigner",
"LocalRevocationCache",
"OIDCBridgeConfig",
Expand All @@ -53,4 +79,8 @@
"SidecarError",
"SidecarStatus",
"TokenExchangeResult",
"CompositeTraceEmitter",
"LedgerQueueItem",
"TaskIdentityRecord",
"UsageCreditRecord",
]
Loading