JWT-aware context + file-backed approval store + TTL enforcement + audit sink BOUNDARY · additive on top of Phase 2a · 24/24 parity preserved
Phase 2b
Additive only — every Phase 2a endpoint is unchanged and verify_examples.py still passes 24/24. Phase 2b adds: (1) Bearer JWT resolver (decode-only unless PyJWT + POLICY_JWT_VERIFY=true + key), (2) a file-backed approval store loaded at startup with rows grounded in approval_queue_model.queue_shape.item_fields, (3) TTL-aware validation that honours sla_due_at and dual-signer requirements, (4) an audit sink BOUNDARY — POST /api/policy/audit/preview renders envelopes matching audit_event_model.base_shape but does not deliver to Kafka or WORM (sink_status=deferred).
Base URL
1 · JWT / verified auth boundary
Resolution order: Bearer > X-PTT-* > body > query > defaults. Verification needs ALL three: PyJWT installed · POLICY_JWT_VERIFY=true · POLICY_JWT_PUBLIC_KEY or POLICY_JWKS_URL. Without them the service still decodes the payload but adds warnings: ['auth_not_verified'].
2 · Approval store
File-backed · loaded at startup from ADMIN_APPROVAL_STORE_PATH (default approval_examples.json). Read-only in Phase 2b (no create / sign / withdraw endpoints — those are Phase 2c). Row shape mirrors approval_queue_model.queue_shape.item_fields.
3 · TTL / expiry semantics · sensitive.check.2b
ttl_remaining = max(0, sla_due_at - now). Engine fail-safe: expired approvals are treated as absent (per access_policy.precedence_rules.prec-8). signed_partial counts as valid only on single-signer rows; dual-signer rows need signed_full.
For a REAL policy-first / eval-second chain against the Feature Flags service, use the cross-runtime demo.
Renders an envelope matching audit_event_model.base_shape for any (actor, action, subject) tuple. Nothing is delivered — sink_status='deferred' on every response. Stdout also logs the envelope as NDJSON so operators can tail the service log and see what would be produced.