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.
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
ff.wizard.interactive_draft
none · scaffold visible to all
ff.enterprise_upload.preview
none · scaffold visible to all
ff.intake_workspace.preview
none · scaffold visible to all
ff.generated_assets.local_notes
none · scaffold visible to all
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.