{
  "schema_version": "1.0",
  "baseline": "A-admin-control-plane-batch-4-readiness",
  "phase": "2b → 2c readiness",
  "updated_at": "2026-04-19",
  "owner": "session_a",
  "purpose_th": "Checklist สำหรับ Admin Control Plane ก่อน cutover ไป db-backed · คู่ขนานกับ FF cutover · ระบุ blocker · cross-service boundary · rollback strategy",
  "purpose_en": "Cutover readiness checklist for Admin Control Plane Service. Parallel to the FF readiness checklist. Identifies blockers, cross-service boundaries with FF, and rollback strategy. Phase 2b ships file-backed. 2c target is db-backed.",
  "honest_note": "Admin Control Plane in Phase 2b is file-backed on approval_examples.json. No admin schema exists in Postgres. No admin audit sink delivery is wired (boundary-only emission). This checklist is the 2c gate.",
  "groups": [
    {
      "group": "A. Persistence · schema",
      "items": [
        { "id": "A.1", "text": "admin schema created in Postgres", "current_status": "NOT_READY" },
        { "id": "A.2", "text": "admin.approval table with state CHECK constraint", "current_status": "NOT_READY" },
        { "id": "A.3", "text": "admin.signature table with UNIQUE (approval_id, signer_id)", "current_status": "NOT_READY" },
        { "id": "A.4", "text": "admin.approval_ttl_state view returns correct ttl_state per ttl_examples.json", "current_status": "NOT_READY" },
        { "id": "A.5", "text": "admin.audit table with sink_status CHECK and retry_count default 0", "current_status": "NOT_READY" },
        { "id": "A.6", "text": "Seed from approval_examples.json + approval_store_examples.json succeeds", "current_status": "NOT_READY" },
        { "id": "A.7", "text": "Alembic revision history created · migration 0001 creates schema", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "B. Persistence · runtime",
      "items": [
        { "id": "B.1", "text": "asyncpg connection pool in Admin app.py", "current_status": "NOT_READY" },
        { "id": "B.2", "text": "ADMIN_BACKING env var honoured · file vs db switch", "current_status": "NOT_READY" },
        { "id": "B.3", "text": "Health endpoint reports db connectivity", "current_status": "NOT_READY" },
        { "id": "B.4", "text": "All 8 examples in approval_examples.json produce identical validate() output across file and db paths", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "C. TTL computation",
      "items": [
        { "id": "C.1", "text": "File-backed and DB-backed agree on ttl_state for every row in ttl_examples.json", "current_status": "NOT_READY" },
        { "id": "C.2", "text": "Background expiry sweep deferred · passive expiry at read time verified", "current_status": "DOCUMENTED · prec-8 fail-safe declared" },
        { "id": "C.3", "text": "Timezone handling correct · UTC normalise", "current_status": "NOT_READY" }
      ]
    },
    {
      "group": "D. Audit sink delivery",
      "items": [
        { "id": "D.1", "text": "admin.audit table receives writes on every mutation", "current_status": "NOT_READY" },
        { "id": "D.2", "text": "Downstream worker ships sink_status='pending' rows to Kafka ptt.audit.trail", "current_status": "NOT_READY" },
        { "id": "D.3", "text": "Failed shipments retry with exponential backoff up to max_retries=5", "current_status": "NOT_READY" },
        { "id": "D.4", "text": "Operator alert if sink_status='pending' backlog > 1000 rows", "current_status": "NOT_READY" },
        { "id": "D.5", "text": "POST /api/policy/audit/preview continues to return sink_status='deferred' until D.1-D.4 complete", "current_status": "READY · documented fact" }
      ]
    },
    {
      "group": "E. Cross-service boundary with FF",
      "items": [
        { "id": "E.1", "text": "Admin sensitive.check.2b endpoint responds < 50ms p99", "current_status": "NOT_READY · no baseline measured yet" },
        { "id": "E.2", "text": "FF-to-Admin HTTP call has 1s timeout with circuit breaker", "current_status": "NOT_READY · FF side not wired" },
        { "id": "E.3", "text": "Admin publishes approval_ttl_refresh on ptt.ff.invalidate channel on state changes", "current_status": "NOT_READY · Redis not wired on Admin side" },
        { "id": "E.4", "text": "FF receives and processes approval_ttl_refresh events within 5s", "current_status": "NOT_READY · FF subscriber not wired" },
        { "id": "E.5", "text": "admin.audit.ingest endpoint accepts FF-emitted audit events", "current_status": "NOT_READY · endpoint not created" }
      ]
    },
    {
      "group": "F. Signer workflow readiness",
      "items": [
        { "id": "F.1", "text": "Signer-facing UI (out of this batch · Admin 2d) has access to admin.approval + admin.signature", "current_status": "OUT_OF_SCOPE_BATCH_4" },
        { "id": "F.2", "text": "Signatures can be submitted via POST /api/policy/approvals/{id}/sign", "current_status": "NOT_READY · endpoint exists in 2b read-only only" },
        { "id": "F.3", "text": "Required signer count enforced server-side before state=signed_full", "current_status": "NOT_READY" },
        { "id": "F.4", "text": "Withdrawal path (state=withdrawn) closes approval without consuming signatures", "current_status": "NOT_READY · state transitions not wired" }
      ]
    },
    {
      "group": "G. Security",
      "items": [
        { "id": "G.1", "text": "JWT verified mode on Admin side · RS256 + JWKS · aud=pty-admin-policy", "current_status": "NOT_READY · Admin 2b uses decode-only same as FF" },
        { "id": "G.2", "text": "RBAC enforced for approval mutations (admin/staff roles only)", "current_status": "PARTIAL · policy_contract describes · policy_engine enforces" },
        { "id": "G.3", "text": "Audit trail captures actor + auth_source on every mutation", "current_status": "DOCUMENTED_ONLY · sink_status='deferred'" }
      ]
    },
    {
      "group": "H. Rollback",
      "items": [
        { "id": "H.1", "text": "ADMIN_BACKING=file restores file-backed path instantly", "current_status": "NOT_READY" },
        { "id": "H.2", "text": "approval_examples.json kept as snapshot for 14 days after cutover", "current_status": "NOT_READY · policy documented only" },
        { "id": "H.3", "text": "Rollback runbook exists and cross-references FF cutover_readiness_checklist.json", "current_status": "DOCUMENTED_HERE · runbook not created" }
      ]
    }
  ],
  "summary_counts": {
    "total_items": 28,
    "ready": 1,
    "partial": 1,
    "documented_only": 2,
    "out_of_scope": 1,
    "not_ready": 23
  },
  "blocking_relations_with_ff": [
    "FF cannot enter verified mode until Admin E.1 (sensitive.check.2b p99) meets SLA",
    "FF cannot ship approval_ttl_refresh subscriber until Admin E.3 publishes events",
    "FF cannot deliver audit events to Admin until E.5 endpoint exists",
    "Both services must cut over to db-backed in lock-step OR neither can · operator must coordinate"
  ],
  "cutover_authority": {
    "who_signs_off": "platform_lead + security_lead",
    "required_artefacts": [
      "This checklist green end-to-end",
      "FF cutover_readiness_checklist.json green end-to-end",
      "Rollback runbook",
      "48-hour parity observation in staging"
    ]
  },
  "pair_with": {
    "phase_2b_contract": "docs/runtime/admin-control-plane-service/phase_2b_contract.json",
    "persistence_schema": "docs/runtime/admin-control-plane-service/persistence_schema.json",
    "approval_store_examples": "docs/runtime/admin-control-plane-service/approval_store_examples.json",
    "ttl_examples": "docs/runtime/admin-control-plane-service/ttl_examples.json",
    "audit_sink_contract": "docs/runtime/admin-control-plane-service/audit_sink_contract.json",
    "ff_cutover_checklist": "docs/runtime/feature-flags-service/cutover_readiness_checklist.json",
    "ff_parity_matrix": "docs/runtime/feature-flags-service/parity_test_matrix.json"
  }
}
