Skip to content

Commit 347f224

Browse files
committed
local identity
1 parent b583784 commit 347f224

10 files changed

Lines changed: 1381 additions & 29 deletions

File tree

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,46 @@ predicate-authorityd \
171171
--local-idp-audience "api://predicate-authority"
172172
```
173173

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+
174214
### How to run with control-plane shipping (out-of-the-box)
175215

176216
```bash

docs/authorityd-operations.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,69 @@ authority decision pushes:
6969
- audit events -> `/v1/audit/events:batch`
7070
- usage credits -> `/v1/metering/usage:batch`
7171

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+
72135
Expected startup output:
73136

74137
```text

predicate_authority/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ Core pieces:
99
- `LocalMandateSigner` for signed short-lived mandates,
1010
- `InMemoryProofLedger` and optional `OpenTelemetryTraceEmitter`,
1111
- typed integration adapters (including `sdk-python` mapping helpers),
12-
- control-plane client primitives for shipping proof and usage batches to hosted APIs.
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
from predicate_authority.daemon import DaemonConfig, PredicateAuthorityDaemon
2020
from predicate_authority.errors import AuthorizationDeniedError
2121
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+
)
2230
from predicate_authority.mandate import LocalMandateSigner
2331
from predicate_authority.policy import PolicyEngine, PolicyMatchResult
2432
from predicate_authority.policy_source import PolicyFileSource, PolicyReloadResult
@@ -53,6 +61,9 @@
5361
"LocalIdPBridge",
5462
"LocalIdPBridgeConfig",
5563
"LocalCredentialStore",
64+
"LocalIdentityRegistry",
65+
"LocalIdentityRegistryStats",
66+
"LocalLedgerQueueEmitter",
5667
"LocalMandateSigner",
5768
"LocalRevocationCache",
5869
"OIDCBridgeConfig",
@@ -68,5 +79,8 @@
6879
"SidecarError",
6980
"SidecarStatus",
7081
"TokenExchangeResult",
82+
"CompositeTraceEmitter",
83+
"LedgerQueueItem",
84+
"TaskIdentityRecord",
7185
"UsageCreditRecord",
7286
]

predicate_authority/control_plane.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def send_usage_records(self, records: tuple[UsageCreditRecord, ...]) -> bool:
9797
payload = {"records": [asdict(record) for record in records]}
9898
return self._post_json("/v1/metering/usage:batch", payload)
9999

100+
def send_audit_payload(self, payload: Mapping[str, object]) -> bool:
101+
return self._post_json("/v1/audit/events:batch", payload)
102+
100103
def _post_json(self, path: str, payload: Mapping[str, object]) -> bool:
101104
attempts = self.config.max_retries + 1
102105
for attempt in range(attempts):

0 commit comments

Comments
 (0)