{
  "schema_version": "1.0",
  "baseline": "A-feature-flags-batch-4-readiness",
  "phase": "2b+ → 2c readiness",
  "updated_at": "2026-04-19",
  "owner": "session_a",
  "purpose_th": "Checklist ที่ต้องผ่านทั้งหมดก่อนจะเปิด FF DB-backed บน production · แบ่งเป็น 10 กลุ่ม · ทุกข้อระบุ status ณ Batch 4",
  "purpose_en": "Mandatory readiness checklist that must be 100% green before FF Service can flip FF_BACKING=db in production. Grouped by concern. Each item reports current status as of Batch 4. Any NOT_READY item is a cutover blocker.",
  "honest_note": "At the end of Batch 4 EVERY group below has items marked NOT_READY. This is expected — Batch 4 is a readiness documentation pass, not the cutover. The list exists so Phase 2c work has a concrete acceptance gate.",
  "groups": [
    {
      "group": "A. JWT / JWKS",
      "items": [
        { "id": "A.1", "text": "JWKS endpoint reachable from FF host", "current_status": "NOT_READY", "evidence": "FF_JWKS_URL env not set · no test run" },
        { "id": "A.2", "text": "In-process JWKS cache implemented with LRU + TTL", "current_status": "NOT_READY", "evidence": "No auth module in FF service" },
        { "id": "A.3", "text": "Single-flight lock on JWKS refresh", "current_status": "NOT_READY" },
        { "id": "A.4", "text": "Algorithm allowlist enforced · never accept alg=none or HS with public JWKS", "current_status": "NOT_READY" },
        { "id": "A.5", "text": "aud + iss match enforced", "current_status": "NOT_READY" },
        { "id": "A.6", "text": "All JWT claim examples (JWT-EX-01..10) pass through verified path with expected outcomes", "current_status": "NOT_READY" },
        { "id": "A.7", "text": "Rotation overlap window documented AND implemented", "current_status": "DOCUMENTED_ONLY" },
        { "id": "A.8", "text": "dev-fallback disabled in production config (FF_ALLOW_DEV_HEADERS=false)", "current_status": "NOT_READY · config switch not honoured by app.py" }
      ]
    },
    {
      "group": "B. Persistence · schema",
      "items": [
        { "id": "B.1", "text": "ff schema created in Postgres", "current_status": "NOT_READY · docker/ai/postgres has no ff schema" },
        { "id": "B.2", "text": "All 4 tables created with correct DDL", "current_status": "NOT_READY" },
        { "id": "B.3", "text": "All indexes present · confirmed via pg_stat_user_indexes", "current_status": "NOT_READY" },
        { "id": "B.4", "text": "FK constraints behave as expected (CASCADE on delete of flag_registry)", "current_status": "NOT_READY" },
        { "id": "B.5", "text": "Alembic revision history starts clean · migration 0001 creates schema", "current_status": "NOT_READY · no alembic directory" },
        { "id": "B.6", "text": "Seed from registry.json + override_examples.json succeeds", "current_status": "NOT_READY" },
        { "id": "B.7", "text": "Rollback script (drop schema) tested in dev", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "C. Persistence · runtime",
      "items": [
        { "id": "C.1", "text": "asyncpg/SQLAlchemy connection pool in app.py", "current_status": "NOT_READY" },
        { "id": "C.2", "text": "FF_BACKING env var honoured · switches between file and db", "current_status": "NOT_READY" },
        { "id": "C.3", "text": "Health endpoint reports db connectivity", "current_status": "NOT_READY" },
        { "id": "C.4", "text": "Timeout on db queries · 200ms default", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "D. Redis / cache",
      "items": [
        { "id": "D.1", "text": "Redis reachable from FF host", "current_status": "NOT_READY · FF_REDIS_URL not set" },
        { "id": "D.2", "text": "redis-py client lifecycle (startup init · shutdown close)", "current_status": "NOT_READY" },
        { "id": "D.3", "text": "Publisher wired at all 4 invalidation trigger points", "current_status": "NOT_READY" },
        { "id": "D.4", "text": "Subscriber loop started on app startup · reconnect logic tested", "current_status": "NOT_READY" },
        { "id": "D.5", "text": "Single-flight stampede protection in place", "current_status": "NOT_READY" },
        { "id": "D.6", "text": "Key prefix discipline verified (only ptt:ff:* touched)", "current_status": "NOT_READY" },
        { "id": "D.7", "text": "Graceful degradation · uncached operation when Redis unreachable", "current_status": "NOT_READY" },
        { "id": "D.8", "text": "All 6 pub/sub example payloads (PUB-01..06) processed correctly", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "E. Parity harness",
      "items": [
        { "id": "E.1", "text": "parity_runner.py exists and reads parity_test_matrix.json", "current_status": "NOT_READY" },
        { "id": "E.2", "text": "All PT-01..PT-17 vectors pass 100% MUST_MATCH fields", "current_status": "NOT_READY" },
        { "id": "E.3", "text": "Stress vector (PT-08) passes with p99 < 100ms on both paths", "current_status": "NOT_READY" },
        { "id": "E.4", "text": "Harness runs in CI · blocks PR merge on failure", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "F. Admin cross-service",
      "items": [
        { "id": "F.1", "text": "FF calls Admin sensitive.check.2b with approval_ref · success path verified", "current_status": "NOT_READY · call not wired from FF" },
        { "id": "F.2", "text": "Admin unreachable → FF fail-safe deny · warning=['admin_unreachable']", "current_status": "NOT_READY" },
        { "id": "F.3", "text": "approval_ttl_refresh subscriber in FF updates local cache promptly", "current_status": "NOT_READY" },
        { "id": "F.4", "text": "Audit events from FF writes reach Admin audit sink (not direct DB write)", "current_status": "NOT_READY · sink delivery deferred" }
      ]
    },
    {
      "group": "G. Observability",
      "items": [
        { "id": "G.1", "text": "Prometheus metrics exposed on /metrics", "current_status": "NOT_READY" },
        { "id": "G.2", "text": "Cache hit/miss/invalidate counters emitted", "current_status": "NOT_READY" },
        { "id": "G.3", "text": "Structured logs emit auth_source + backing_state on every evaluate", "current_status": "NOT_READY" },
        { "id": "G.4", "text": "Dashboard: FF parity divergence alert exists", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "H. Security",
      "items": [
        { "id": "H.1", "text": "TLS enforced on Redis + Postgres connections in prod", "current_status": "NOT_READY · local dev uses plain" },
        { "id": "H.2", "text": "Database credentials loaded from GCP Secret Manager (not env in plain)", "current_status": "NOT_READY" },
        { "id": "H.3", "text": "FF_ALLOW_DEV_HEADERS=false verified in prod config", "current_status": "NOT_READY" },
        { "id": "H.4", "text": "Rate limiting on /v1/flags/evaluate · 500 rps per tenant default", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "I. Rollback",
      "items": [
        { "id": "I.1", "text": "FF_BACKING=file restores file-backed path instantly (no deploy)", "current_status": "NOT_READY" },
        { "id": "I.2", "text": "File-backed path kept warm for 14 days after cutover", "current_status": "NOT_READY · policy documented only" },
        { "id": "I.3", "text": "DB reads can be disabled via ENV flip if db goes down", "current_status": "NOT_READY" },
        { "id": "I.4", "text": "Rollback runbook exists and references this checklist", "current_status": "DOCUMENTED_HERE · runbook not created" }
      ]
    },
    {
      "group": "J. Operator readiness",
      "items": [
        { "id": "J.1", "text": "On-call knows where FF_BACKING switch lives", "current_status": "NOT_READY" },
        { "id": "J.2", "text": "Parity divergence alert routes to Slack + PagerDuty", "current_status": "NOT_READY" },
        { "id": "J.3", "text": "Admin 2b cutover_readiness_notes.json cross-referenced in operator runbook", "current_status": "NOT_READY" },
        { "id": "J.4", "text": "Smoke test against staging before prod cutover", "current_status": "NOT_READY" }
      ]
    }
  ],
  "summary_counts": {
    "total_items": 42,
    "ready": 0,
    "documented_only": 2,
    "not_ready": 40,
    "blocker_interpretation": "Any NOT_READY item is a cutover blocker. DOCUMENTED_ONLY items need implementation work to become READY."
  },
  "cutover_authority": {
    "who_signs_off": "platform_lead + on_call_lead",
    "required_artefacts_for_signoff": [
      "This checklist green end-to-end",
      "parity_test_matrix.json · 100% pass",
      "cutover_parity_matrix.json · all MUST_MATCH verified",
      "Rollback runbook · linked in on-call wiki",
      "Admin 2b cutover_readiness_notes.json · aligned on cross-service boundary"
    ]
  },
  "pair_with": {
    "parity_test_matrix": "docs/runtime/feature-flags-service/parity_test_matrix.json",
    "cutover_parity_matrix": "docs/runtime/feature-flags-service/cutover_parity_matrix.json",
    "admin_cutover_notes": "docs/runtime/admin-control-plane-service/cutover_readiness_notes.json",
    "jwks_notes": "docs/runtime/feature-flags-service/jwks_integration_notes.json",
    "persistence_schema": "docs/runtime/feature-flags-service/persistence_schema.json",
    "cache_runtime_notes": "docs/runtime/feature-flags-service/cache_runtime_notes.json"
  }
}
