{
  "schema_version": "1.0",
  "baseline": "A-feature-flags-phase-2b-plus",
  "phase": "2b+",
  "updated_at": "2026-04-19",
  "owner": "session_a",
  "purpose_th": "บันทึกสถานะจริงของ FF Service persistence backend · current implementation · planned RDBMS/Redis · precedence · honest gaps · migration plan",
  "purpose_en": "Authoritative notes on the current and planned FF Service persistence backend. Describes the file-backed current state, the planned RDBMS/Redis layout, precedence model, honest gaps, and migration plan.",
  "honest_note": "No database. No Redis. No write-ahead log. FF Service in Phase 2b+ runs entirely on file-backed JSON with in-process read cache. This file documents the shape so Phase 2c migration can conform.",
  "current_state": {
    "mode": "file-backed JSON + in-process memory",
    "read_path": "read at startup → in-memory dict → served for lifetime of process",
    "write_path": "mutations append to the JSON file + update in-memory dict · no fsync discipline · no backup · dev-only",
    "files": [
      { "role": "flag_registry", "path": "docs/runtime/feature-flags/registry.json", "owner": "A", "mutability": "rare (registry edits · manual)" },
      { "role": "override_examples (acting as store)", "path": "docs/runtime/feature-flags-service/override_examples.json", "owner": "A", "mutability": "frequent via /v1/flags/override/* · dev-only" },
      { "role": "override_store_schema", "path": "docs/runtime/feature-flags-service/override_store_schema.json", "owner": "A", "mutability": "schema-only, rarely changed" }
    ],
    "consistency": "process-local · multi-replica deployment would drift immediately"
  },
  "precedence_model": {
    "order": [
      "1. User override (if present · not expired)",
      "2. Tenant override (if present · not expired)",
      "3. Flag default (from registry)"
    ],
    "ties_broken_by": "more-specific wins (user > tenant > default)",
    "expiry_enforcement": "read-time · rows with expires_at in the past are ignored (passive expiry)",
    "honest_note": "No active expiry sweep · no background worker · relies on read-time check"
  },
  "planned_backend": {
    "primary": {
      "engine": "PostgreSQL 15+",
      "tables": [
        {
          "name": "ff_flag",
          "columns": [
            "flag_id TEXT PRIMARY KEY",
            "default_enabled BOOLEAN NOT NULL",
            "sensitive BOOLEAN NOT NULL DEFAULT FALSE",
            "requires_approval BOOLEAN NOT NULL DEFAULT FALSE",
            "approval_matrix_row TEXT NULL",
            "description TEXT",
            "created_at TIMESTAMPTZ NOT NULL",
            "updated_at TIMESTAMPTZ NOT NULL"
          ]
        },
        {
          "name": "ff_tenant_override",
          "columns": [
            "tenant_id TEXT NOT NULL",
            "flag_id TEXT NOT NULL REFERENCES ff_flag(flag_id)",
            "enabled BOOLEAN NOT NULL",
            "reason TEXT",
            "expires_at TIMESTAMPTZ NULL",
            "created_by TEXT NOT NULL",
            "created_at TIMESTAMPTZ NOT NULL",
            "PRIMARY KEY (tenant_id, flag_id)"
          ],
          "indexes": ["(tenant_id)", "(expires_at)"]
        },
        {
          "name": "ff_user_override",
          "columns": [
            "user_id TEXT NOT NULL",
            "flag_id TEXT NOT NULL REFERENCES ff_flag(flag_id)",
            "enabled BOOLEAN NOT NULL",
            "reason TEXT",
            "expires_at TIMESTAMPTZ NULL",
            "created_by TEXT NOT NULL",
            "created_at TIMESTAMPTZ NOT NULL",
            "PRIMARY KEY (user_id, flag_id)"
          ],
          "indexes": ["(user_id)", "(expires_at)"]
        },
        {
          "name": "ff_audit",
          "columns": [
            "audit_id TEXT PRIMARY KEY",
            "event_type TEXT NOT NULL",
            "actor TEXT NOT NULL",
            "flag_id TEXT",
            "tenant_id TEXT",
            "user_id TEXT",
            "before JSONB",
            "after JSONB",
            "approval_ref TEXT",
            "ts TIMESTAMPTZ NOT NULL"
          ],
          "indexes": ["(ts DESC)", "(actor, ts DESC)", "(flag_id, ts DESC)"]
        }
      ]
    },
    "secondary_cache": {
      "engine": "Redis 7+",
      "key_prefix": "ptt:ff:",
      "ttl_map": {
        "flag": 300,
        "override": 60,
        "evaluated": 30,
        "jwks": 3600
      },
      "channel": "ptt.ff.invalidate (pub/sub)",
      "notes": "See cache_invalidation_contract.json"
    },
    "current_gap": [
      "No DB client in app.py · no alembic · no migration history",
      "No Redis client · no channel publisher",
      "No ff_audit records · audit emission is boundary-only per phase_2b_contract",
      "No read-replica strategy · no write-sharding · no multi-region"
    ]
  },
  "migration_plan": {
    "phase": "2c",
    "steps": [
      "1. Stand up Postgres (dev first · managed prod later) · create schema · seed from current JSON",
      "2. Introduce DB client in app.py · parity test: same request on file-backed vs DB backed must yield same response for 100% of override_examples rows",
      "3. Switch override write path to DB · keep file-backed read path as fallback for 1 sprint",
      "4. Stand up Redis · wire /v1/flags/evaluate to consult Redis first · on miss hit DB · warm cache",
      "5. Enable invalidation publisher on mutation paths · subscriber keeps local read cache warm",
      "6. Remove file-backed code path once DB+Redis parity locked for 14 days"
    ],
    "honest_note": "This plan is written for Phase 2c consumers. 2b+ ships nothing from this list. Tracked here so any PR promising backend work has a conformable target."
  },
  "safety_rails": [
    "No secrets in these JSON files · all credentials via env at Phase 2c",
    "No tenant_id collisions accepted — reject write if tenant-id already has the flag record unless force=true (DB path)",
    "expires_at must be in the future on write · reject otherwise",
    "Mutations require an approval_ref from Admin 2b when flag.requires_approval=true (consumed via policy-aware gating)"
  ],
  "pair_with": {
    "contract": "docs/runtime/feature-flags-service/phase_2b_plus_contract.json",
    "cache": "docs/runtime/feature-flags-service/cache_invalidation_contract.json",
    "jwt": "docs/runtime/feature-flags-service/jwt_verification_notes.json",
    "schema_file_backed": "docs/runtime/feature-flags-service/override_store_schema.json"
  }
}
