{
  "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": "เมตริกซ์เทียบผลระหว่าง file-backed กับ db-backed · ระบุว่า field ใดห้ามต่าง · field ใดต่างได้ (meta) · เป็นเกณฑ์สำหรับ cutover decision",
  "purpose_en": "Field-by-field parity matrix between file-backed and db-backed implementations of /v1/flags/evaluate and /v1/flags/override/*. Every field marked MUST_MATCH has to be byte-identical. MAY_DIFFER fields are allowed to diverge only on meta.* entries that describe provenance.",
  "honest_note": "No harness has executed this matrix yet. Execution requires both paths to coexist (dual_write_parity state) which ships in Phase 2c. This document is the pass/fail criteria the harness will apply.",
  "endpoints_in_scope": [
    "GET  /api/flags/eval",
    "POST /api/flags/eval/batch",
    "GET  /api/flags/registry",
    "POST /v1/flags/override/tenant/{tenant_id}/{flag_id}",
    "POST /v1/flags/override/user/{user_id}/{flag_id}",
    "POST /v1/flags/sensitive/check"
  ],
  "field_matrix": [
    { "field": "enabled", "rule": "MUST_MATCH", "rationale": "core evaluation answer · any divergence blocks cutover" },
    { "field": "flag_id", "rule": "MUST_MATCH", "rationale": "identifier" },
    { "field": "reason", "rule": "MUST_MATCH", "rationale": "precedence path must match · 'user_override' / 'tenant_override' / 'default'" },
    { "field": "reason_chain", "rule": "MUST_MATCH", "rationale": "ordered list · any reorder is a divergence" },
    { "field": "source", "rule": "MUST_MATCH", "rationale": "which layer served the answer: user_override | tenant_override | default" },
    { "field": "expires_at", "rule": "MUST_MATCH", "rationale": "TTL semantics must agree · passive expiry only" },
    { "field": "approval_ref", "rule": "MUST_MATCH", "rationale": "sensitive gating cross-ref · mismatch would mean divergent audit trail" },
    { "field": "warnings", "rule": "MUST_MATCH_AS_SET", "rationale": "order-insensitive set equality · duplicates not allowed" },
    { "field": "auth_source", "rule": "MUST_MATCH", "rationale": "auth resolution path" },
    { "field": "verified", "rule": "MUST_MATCH", "rationale": "verification result" },
    { "field": "meta.backing_state", "rule": "MAY_DIFFER", "rationale": "expected to differ: 'file_backed' vs 'db_backed' · this is the ONLY expected divergence" },
    { "field": "meta.evaluated_at", "rule": "MAY_DIFFER_WITHIN_SKEW", "rationale": "timestamps may differ by harness request ordering · must be within 5 seconds" },
    { "field": "meta.cache", "rule": "MAY_DIFFER", "rationale": "file-backed path is uncached · db-backed path may report hit/miss once Redis is wired" },
    { "field": "meta.approval_ref", "rule": "MUST_MATCH", "rationale": "approval ref is authoritative · differs only if write path forked" },
    { "field": "meta.ttl_state", "rule": "MUST_MATCH", "rationale": "valid/near-expiry/expired/stale/unknown · mirrors ttl_examples.json" }
  ],
  "test_vectors": [
    {
      "id": "PV-01",
      "scenario": "No override · flag.default=true",
      "input": { "user": "U1", "tenant": "T1", "flag": "ff.a" },
      "expected": { "enabled": true, "source": "default", "reason": "default", "warnings_contains": [], "verified": false }
    },
    {
      "id": "PV-02",
      "scenario": "Tenant override false · flag.default=true · user has no override",
      "input": { "user": "U1", "tenant": "T-pty-pilot-01", "flag": "ff.b" },
      "expected": { "enabled": false, "source": "tenant_override", "reason_chain_has": ["tenant_override", "default"] }
    },
    {
      "id": "PV-03",
      "scenario": "User override true · tenant override false · precedence user",
      "input": { "user": "U-promo", "tenant": "T-pty-pilot-01", "flag": "ff.b" },
      "expected": { "enabled": true, "source": "user_override", "reason": "user_override_precedence", "reason_chain_has": ["user_override", "tenant_override", "default"] }
    },
    {
      "id": "PV-04",
      "scenario": "Tenant override past expires_at · must fall through to default",
      "input": { "user": "U1", "tenant": "T-pty-pilot-exp", "flag": "ff.c" },
      "expected": { "enabled": false, "source": "default", "warnings_contains": ["tenant_override_expired"] }
    },
    {
      "id": "PV-05",
      "scenario": "Flag requires_approval=true · approval_ref valid",
      "input": { "user": "U-sensitive", "tenant": "T-pty-pilot-01", "flag": "ff.sensitive_preview", "approval_ref": "APP-100001-abcd" },
      "expected": { "enabled": true, "meta": { "approval_ref": "APP-100001-abcd", "ttl_state": "valid" } }
    },
    {
      "id": "PV-06",
      "scenario": "Flag requires_approval=true · approval_ref expired",
      "input": { "user": "U-sensitive", "tenant": "T-pty-pilot-01", "flag": "ff.sensitive_preview", "approval_ref": "APP-100002-stale" },
      "expected": { "enabled": false, "reason": "approval_expired", "meta": { "ttl_state": "expired" } }
    },
    {
      "id": "PV-07",
      "scenario": "Sensitive flag · no approval_ref · verified mode",
      "input": { "user": "U-sensitive", "tenant": "T-pty-pilot-01", "flag": "ff.sensitive_preview" },
      "expected": { "enabled": false, "reason": "requires_approval" }
    },
    {
      "id": "PV-08",
      "scenario": "Sensitive flag · approval valid · but auth unverified",
      "input": { "user": "U-sensitive", "tenant": "T-pty-pilot-01", "flag": "ff.sensitive_preview", "approval_ref": "APP-100001-abcd", "auth_mode": "decode-only" },
      "expected": { "enabled": false, "reason": "auth_not_verified" }
    }
  ],
  "pass_criteria": {
    "mandatory": "100% MUST_MATCH fields identical across all active vectors",
    "tolerance_for_may_differ": "meta.evaluated_at within 5s skew · meta.backing_state is expected to differ",
    "regression_gate": "cutover is BLOCKED if even ONE MUST_MATCH field diverges on any vector"
  },
  "rollback_criteria": {
    "triggers": [
      "Any MUST_MATCH divergence detected in production traffic replay",
      "Error rate on db-backed path > 0.1% over 15 minute window",
      "p95 latency on db-backed path > 2× file-backed baseline",
      "audit table growth rate indicates write amplification"
    ],
    "rollback_steps": [
      "1. Flip FF_BACKING=file (env-var controlled · runtime switchable)",
      "2. Keep db writes enabled (dual_write) · but reads served from file",
      "3. Investigate · fix · re-run parity harness · decide again"
    ]
  },
  "harness_location_target": "docs/runtime/feature-flags-service/parity_runner.py (not_created_yet)",
  "pair_with": {
    "persistence_schema": "docs/runtime/feature-flags-service/persistence_schema.json",
    "parity_test_matrix": "docs/runtime/feature-flags-service/parity_test_matrix.json",
    "cutover_readiness_checklist": "docs/runtime/feature-flags-service/cutover_readiness_checklist.json",
    "verify_parity_existing": "docs/runtime/feature-flags-service/verify_parity.py (current JS↔Py parity · different purpose)"
  }
}
