Feature Flags · Phase 2b+ Hardening Planning · verification modes · persistence backend · cache invalidation · policy-aware gating · rollout visibility

Planning · v1.0 · hardening pass
2b+ is a documentation/contract pass on top of Phase 2b. No new endpoints. No new persistence. No Redis. Runtime still uses file-backed JSON. What 2b+ ships is: formal verification-mode matrix · persistence-backend honest-note · cache-invalidation shape · policy-aware sensitive-gating relation with Admin 2b · rollout-visibility pointers to Batch-1 runtimes. Every artefact references its real gap so Phase 2c can wire real infra without redesigning semantics.

Artefacts shipped in 2b+

Verification modes

verified

Enabled: false
Bearer JWT verified against JWKS · aud+iss match · full claim trust
Current state: NOT WIRED · Phase 2c

decode-only

Enabled: true
Bearer decoded (base64 claims) but signature NOT verified · warnings=['auth_not_verified']
Current state: SHIPPING in 2b+ runtime

dev-fallback

Enabled: true · dev only
X-PTT-User-Id / Tenant-Id / Role headers or body/query · warnings=['dev_mode']
Current state: SHIPPING · FF_ALLOW_DEV_HEADERS must be false in prod (planned)

anonymous

Enabled: true
No source · defaults apply · evaluation proceeds with no claims
Current state: SHIPPING

Persistence backend · current vs planned

Layer
Current (2b+)
Planned (2c)
Flag registry
file-backed JSON · docs/runtime/feature-flags/registry.json
PostgreSQL ff_flag table · seeded from JSON · parity-tested
Tenant overrides
file-backed JSON · override_examples.json (acting as store)
PostgreSQL ff_tenant_override · indexes (tenant_id), (expires_at)
User overrides
same JSON file · keyed by user_id
PostgreSQL ff_user_override · indexes (user_id), (expires_at)
Cache layer
NONE · in-process dict only
Redis · prefix ptt:ff: · TTL per namespace · pub/sub invalidate
Audit
NONE · emission is boundary-shape-only (per Admin 2b)
ff_audit table + WORM sink · delivered via audit_sink_contract

Cache invalidation shape

Channel ptt.ff.invalidate · key prefix ptt:ff: · 4 namespaces (flag / override / evaluated / jwks) · TTLs 300/60/30/3600s · 4 invalidation triggers (override_mutation / registry_reload / ttl_expiry / manual_op). Strategy: soft-TTL + refresh-ahead to avoid stampede. Current state: NOT WIRED. Runtime reads file-backed JSON on every call.

See cache_invalidation_contract.json for the authoritative shape.

Policy-aware sensitive gating · relation to Admin 2b

When a flag has sensitive_flag=true or requires_approval=true, FF must call Admin 2b POST /api/policy/sensitive.check.2b with {target_type:'feature_flag', target_id, matrix_row}. Admin 2b returns {decision, ttl_state, approval_ref?}. FF applies allow/deny/allow-with-warning accordingly and surfaces approval_ref + ttl_state in the response meta.

Current state: MODELED. The cross-service HTTP call is documented in the contract but not implemented in app.py yet. Fallback on admin-unreachable is deny (fail-safe).

Rollout visibility · Batch-1 runtime surfaces

Runtime
Flag candidate
Current gate
wizard
ff.wizard.interactive_draft
none · scaffold visible to all
enterprise-upload
ff.enterprise_upload.preview
none · scaffold visible to all
intake-workspace
ff.intake_workspace.preview
none · scaffold visible to all
generated-assets
ff.generated_assets.local_notes
none · scaffold visible to all
daily-queue
ff.daily_queue.simulation
none · simulation visible to all

Use cases (5) modeled only · executable end-to-end in Phase 2c

UC-FF2B+-01

Verified vs decode-only JWT mode comparison

Same Authorization header → FF-A with FF_VERIFY_MODE=verified requires JWKS + aud match and may 401; FF-B with FF_VERIFY_MODE=decode-only returns the evaluation with warnings=['auth_not_verified']. Caller decides whether to trust based on surfaced warnings.
UC-FF2B+-02

Tenant override with current backing explanation

PUT /v1/flags/override/tenant/T-pty/ff.example · file-backed JSON mutated · in-memory dict refreshed · response shows backing_state='file_backed' · honest warning reminds caller this will not survive a multi-replica deployment until 2c DB migration.
UC-FF2B+-03

User override with precedence explanation

Tenant override says false · user override says true · evaluation for user U-9 returns enabled=true with reason=user_override_precedence and backing_state=file_backed. Meta shows the override chain consulted top to bottom.
UC-FF2B+-04

Cache invalidation / refresh semantics

2b+ runtime has no cache · every evaluate re-reads JSON · response meta.cache='uncached'. Phase 2c adds Redis · meta.cache shifts to hit/miss · invalidation broadcast on mutation keeps replicas in sync. 2b+ documents the channel so 2c wires against this contract.
UC-FF2B+-05

Sensitive flag gated through Admin relation

Flag ff.cm.advanced_policy has sensitive_flag=true · requires_approval=true · approval_matrix_row=row-sensitive-override. Evaluation consults Admin 2b sensitive.check.2b with a passed-in JWT's approval_ref. Admin returns ttl_state=valid → FF allows with approval_ref in meta. If ttl_state=expired → FF denies with reason=approval_expired.

Deferred matrix

Item
Current state
Next phase
JWT verified-mode wiring
documented · no JWKS
2c IdP integration
RDBMS persistence
file-backed JSON
2c migration + parity
Redis cache
no client · no channel
2c pub/sub wiring
Transition/flip/rollback API
no endpoint
2c after audit delivery
Tenant cohort rollout
descriptive only
2c
Phased-rollout %
none
2c
Rate limiting
none · relies on gateway
2c
Production deployment hardening
run.sh local dev only
future · platform decision

Peer references