
# Yapılandırma değişikliği planla

`POST /v1/config-plans` · izin `config:read` ve belgedeki her türün okuma izni

Bir yapılandırma belgesini ortamın güncel yapılandırmasıyla karşılaştırır ve sonucu plan olarak saklar. Hiçbir şey değişmez: plan yalnız belgenin uygulanınca ne yapacağını gösterir. `development`'tan `staging`'e ya da `production`'a terfiyi uygulamadan önce incelemek için kullanın.

## İstek

| Alan | Açıklama |
|---|---|
| `document` | Zorunlu. [`GET /v1/config-export`](../config-export/get.md) biçiminde bir yapılandırma belgesi. `https://docs.keenauth.com/config/schema/v1.json` JSON Schema'sına uymalıdır. |
| `prune` | İsteğe bağlı, varsayılan `false`. `true` ise belgenin türlerinden belgede olmayan kaynakların silinmesi planlanır. |

- Belgenin türleri verildiyse `kinds`'tir (o zaman her kaynak listelenen bir türden olmalıdır), verilmediyse kaynakların türleridir. Yalnız bu türler karşılaştırılır; diğer türler olduğu gibi kalır.
- Belgede olmayan bir kaynak `prune` `true` olmadıkça **silinmez**. Tekil türler (`environment_settings`, `identity_settings`, `branding`) hiç silinmez.
- Tekil türlerde belgede olmayan bir alan değişmez: hedefin mevcut değeri korunur. Böylece alan eklenmeden önce dışa aktarılmış bir belge o alanı sıfırlamaz. Açıkça verilen değer, `null` dahil, yine yazılır. Diğer türlerde belgede olmayan alan varsayılan değerini alır.
- Belge gizli değer içeremez. Gizli alan yalnız `secret_ref` yer tutucusu ya da `null` taşır; değer reddedilir ve yanıtta asla yankılanmaz.
- Belgedeki `warnings` yok sayılır; en çok 15.000 uyarı kabul edilir (bir dışa aktarım hiçbir zaman daha fazlasını içermez).
- İzinler aynı türlerin dışa aktarımının izinleridir. `prune` ile silinen bir kaynağa başvurabilecek türler de okunur, bu yüzden onların izinleri de gerekir.

## Yanıt

`201 Created`, `Location: /v1/config-plans/{plan_id}` ve `Cache-Control: no-store` ile.

| Alan | Açıklama |
|---|---|
| `id` | Plan kimliği, `cfgp_…`. |
| `status` | `planned`; `expires_at`'ten sonra `expired`. |
| `expires_at` | Plan 1 saat geçerlidir. Süresi dolduktan 7 gün sonra silinir. |
| `summary` | `create`, `update`, `delete` ve `no_op` eylemlerinin sayısı. |
| `changes` | Kaynak başına bir kayıt; tür sırasıyla, sonra `key`'e göre. |
| `warnings` | `secret_required`: bağlantı, hedefte henüz olmayan bir istemci sırrı istiyor. |

Her değişiklik `kind`, `key`, `action` (`create`, `update`, `delete` ya da `no_op`), `object_version` (hedef kaynağın plan anındaki sürümü; `create`'te `null`), `requires_secret`, `fields` ve `create` ile `update`'te istenen `spec`'i taşır. `fields`'taki her kayıt bir alan farkıdır: `pointer` (RFC 6901, örneğin `/spec/name`), `op` (RFC 6902'deki gibi `add`, `remove` ya da `replace`), `before` ve `after`. Listeler bütün değer olarak karşılaştırılır.

Dışa aktarılan bir belge değiştirilmeden verilirse, kaynakların ve küme anlamlı listelerin sırası ne olursa olsun her kaynak için `no_op` planlanır.

```json
{
  "id": "cfgp_01m4qpqtywfs7930tb6kqn5rc5",
  "status": "planned",
  "prune": false,
  "kinds": ["connection"],
  "expires_at": "2026-10-12T11:00:00Z",
  "summary": {"create": 1, "update": 1, "delete": 0, "no_op": 1},
  "changes": [
    {"kind": "connection", "key": "db", "action": "no_op", "object_version": 5, "requires_secret": false, "fields": []},
    {"kind": "connection", "key": "github", "action": "create", "object_version": null, "requires_secret": true, "fields": [], "spec": {"type": "social", "strategy": "github", "name": "GitHub", "display_name": null, "status": "active", "settings": {}, "client_secret": {"secret_ref": {"source": "env", "name": "KEENAUTH_CONNECTION_GITHUB_CLIENT_SECRET"}}}},
    {"kind": "connection", "key": "google", "action": "update", "object_version": 4, "requires_secret": false, "fields": [{"pointer": "/spec/name", "op": "replace", "before": "Google", "after": "Google Workspace"}], "spec": {"type": "social", "strategy": "google", "name": "Google Workspace", "display_name": null, "status": "active", "settings": {}, "client_secret": {"secret_ref": {"source": "env", "name": "KEENAUTH_CONNECTION_GOOGLE_CLIENT_SECRET"}}}}
  ],
  "warnings": [{"code": "secret_required", "kind": "connection", "key": "github", "pointer": "/spec/client_secret"}]
}
```

Her plan ortamın yönetim günlüğüne bir `config_plan.created` kaydı yazar.

## Sınırlar

- **Hız:** planların ortam başına kendi dakikalık sınırı vardır: `production` ortamında Free 20, Startup ve Business 60, Enterprise 120; `development` ve `staging` ortamında bunun onda biri (en az 2).
- **Ortam kotası:** en çok 100 uygulanmamış ve süresi dolmamış plan; silinene dek (süre dolduktan 7 gün sonra) en çok 2.000 plan ve 1 GiB saklanan plan. Başka ortamların planları hiç sayılmaz.
- **Kapasite:** sunucu aynı anda sınırlı sayıda büyük plan hesaplar; bir ortam bu kapasitenin en çok dörtte birini kullanabilir.
- **Alan sınırları** yapılandırma servisinin yazarken uyguladığı sınırlardır: şema listeleri ve metinleri sınırlar (ör. `redirect_uris` 50, `allowed_web_origins` 20, `permissions` 1.000), bağlantı `settings`'i en çok 32 KiB ve 8 düzey derinlikte, uygulama `jwks`'i ve her iznin `consent_text_i18n`'i en çok 16 KiB'tır.

## Hatalar

| HTTP | code | Ne zaman |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | Gövde JSON değil ya da `document` ve `prune` dışında bir alan taşıyor. |
| 401 | `unauthenticated` | Kimlik bilgisi eksik, geçersiz ya da iptal edilmiş. |
| 403 | `insufficient_permissions` | Kimlik bilgisinde gerekli bir izin yok; `required_permissions` hepsini listeler. |
| 413 | `config_document_too_large`, `payload_too_large` | Belge 5.000 kaynağı ya da 10 MiB'ı aşıyor; daha az tür planlayın. |
| 422 | `validation_failed` | Aşağıya bakın; `errors[].pointer` isteğin içini gösterir (`/document/...`). |
| 429 | `rate_limited` | Plan hız sınırına ya da ortamın plan kotasına ulaşıldı veya ortam aynı anda çok fazla plan hesaplıyor; `Retry-After` süresinden sonra yeniden deneyin (kotada en eski planın süresi dolunca ya da silinince). |
| 503 | `service_unavailable` | Plan hesaplama kapasitesi dolu ya da bir bağımlılık geçici olarak kullanılamıyor; `Retry-After` süresinden sonra yeniden deneyin. |

| `errors[].code` | Anlamı |
|---|---|
| `required` | `document` yok. |
| `invalid` | Belge bu işaretçide şemaya uymuyor. |
| `duplicate` | İki kaynak aynı `kind` ve `key`'e sahip. |
| `kind_not_listed` | Kaynağın türü `kinds` içinde değil. |
| `secret_value` | Gizli bir değer bulundu; `secret_ref` yer tutucusu kullanın. |
| `unresolved_reference` | Başvurulan `key` değişiklikten sonra var olmayacak. |
| `key_in_use` | `key`, yapılandırmayla yönetilmeyen bir uygulamaya (organizasyona ait ya da kendini kaydetmiş) ya da sistem API'sine ait. |
| `still_referenced` | `/prune`: kalan bir kaynak, planın sileceği bir kaynağa başvuruyor. |
| `too_large` | Bir kaynakta, uyarıda ya da üst düzey alanda 200.000'den fazla JSON değeri var. |
| `too_long` | Bağlantı `settings`'i 32 KiB'ı ya da uygulama `jwks`'i veya bir iznin `consent_text_i18n`'i 16 KiB'ı aşıyor. |
| `too_deep` | Bağlantı `settings`'i 8 düzeyden derin. |
| `reserved` | API `identifier`'ı platformun `urn:idp:` ad alanında. |

## Örnek

```bash
curl -X POST "https://api.eu.keenauth.com/v1/config-plans" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"document\": $(cat keenauth.json), \"prune\": false}"

keenauth config plan -f keenauth.yaml
```
