FF Service · Phase 2b

JWT-aware context + file-backed override store + sensitive-flag precheck · additive on top of Phase 2a+ · parity 19/19 preserved

Base URL

1 · JWT context (Bearer resolver)

Resolution order: Bearer > X-PTT-* > query > defaults. Without PyJWT the service decodes the payload without verifying the signature — every response then carries auth_source=jwt_unverified and warnings: ['auth_not_verified']. This is the honest dev path.

These sample tokens are HS256 with no signing secret. In unverified mode the payload is extracted; in verified mode they would all fail.

2 · Override store

File-backed · loaded at startup from FF_OVERRIDES_PATH (default override_examples.json). Read-only in Phase 2b — no create/delete endpoint. Precedence: inline request.overrides.user > inline request.overrides.tenant > store user-row > store tenant-row > evaluator defaults (stage · bucketing · default_value).

3 · Sensitive-flag precheck

POST /api/flags/sensitive-eval returns the sensitivity profile + a local policy_hint (NOT a real call to admin) + optional flag eval if the gate is open. For a real policy-first / eval-second chain against the admin service, use the cross-runtime integration demo.

Use case presets

UC 1 · Allow JWT present · normal flag read flag=flags.registry_v1 (non-sensitive) · tier=staff Expect: policy_hint=skipped · flag_source=stage-internal
UC 2 · Tenant override wins Tenant override beats default flag=hints.registry_v1 · tenant=pty-zeroth Expect: store tenant row value=true wins over draft stage
UC 3 · User override wins User override beats tenant override flag=hints.registry_v1 · user=U-42 (opt-out) Expect: source=user_override · value=false (user row overrides tenant row)
UC 4a · Sensitive deny Sensitive flag · no approval flag=admin.control_plane_v1 · approval_refs=[] Expect: policy_hint=deny-needs-approval · can_proceed=false
UC 4b · Sensitive allow Sensitive flag · approval_refs present flag=admin.control_plane_v1 · approval_refs=[APP-row-sensitive-override-1] Expect: policy_hint=allow · but evaluator still returns source=approval_missing because registry flag audit.last_approval_ref=null (honest double-gate)

⚠ Honest limits · deferred matrix