İçeriğe geç

MAT-004: Public API And Client State Contract

Geliştirme · 0.0.0-dev

Yayın

Doküman
0.0.0-dev
Uygulama
0.0.0

Bu sayfa

Uygulama
0.0.0
  • Status: Accepted
  • Date: 2026-08-18
  • Decision owners: architecture, API, frontend and security maintainers
  • Scope: public API v1 operation names, envelopes, pagination, client states, generated contracts and query budgets

ADR-010 public API’nin versioned, allowlist tabanlı, release-bound ve RLS ile korunan olmasını şart koşar. Bu belge onun açık bıraktığı concrete wire sözleşmesini kapatır. Machine key’ler ve sayısal bütçeler config/governance/api-contract.yml içindedir; bu belge onların semantik anlamını açıklar.

Bu karar PostgreSQL tablolarını, migration’ı, RPC gövdelerini veya frontend component’lerini üretmez. F3/F4 storage ve RPC’leri, F8 generated client ve UI durumlarını bu sözleşmeden üretir. Public JSON hiçbir zaman MAT-003 fiziksel relation shape’inin doğrudan serialization’ı değildir.

İlk major’ın exposed schema’sı api_v1 olur. Supabase/PostgREST çağrısı /rest/v1/rpc/{rpc_name} yolunu ve api_v1 profile’ını kullanır. Major, operation dispatch edilmeden önce schema ve fiziksel adın _v1 son ekiyle iki kez belirgindir. Profile veya suffix uyuşmazlığı operation lookup yapılmadan unsupported_contract döndürür. Unversioned alias ve function overload yasaktır.

Üç kimlik birbirinden ayrılır:

Kimlik Ömür Örnek
Logical operation key Capability değişmedikçe kalıcı entity_exact_get
Operation revision Request/response/auth/error semantiğinin immutable sürümü deployment içindeki revision UUID
Physical RPC name Bir major içindeki unambiguous PostgREST adı get_entity_exact_v1

Storage relation, helper function, cache veya private provenance adı public operation olamaz. OpenAPI ve generated client yalnız accepted exposure manifest’in public_surface bölümünden üretilir.

Logical operation RPC Amaç Erişim Sayfalama
contract_capabilities_get get_contract_capabilities_v1 Desteklenen major/release/deployment keşfi public Yok
chapter_core_get get_chapter_core_v1 Annotation’dan bağımsız exact chapter core projection public Yok
chapter_headings_get get_chapter_headings_v1 Release-pinned stable heading hiyerarşisi ve chapter TOC projection’ı public Yok
chapter_ottoman_get get_chapter_ottoman_v1 Core koordinatlarına bağlı exact Osmanlıca reader unit’leri public Yok
chapter_hover_get get_chapter_hover_v1 Token/terkip hover binding ve lexical child projection’ları public Yok
chapter_notes_get get_chapter_notes_v1 Exact marker geometry, accepted note body ve explicit source-gap projection’ı public Yok
entity_exact_get get_entity_exact_v1 İstenen historical entity’yi yönlendirmeden getirir public Yok
entity_current_resolve resolve_entity_current_v1 Explicit policy ile current target kümesini çözer public Yok
entity_occurrences_list list_entity_occurrences_v1 Token, compound veya başka entity’nin geçtiği yerler public Cursor
repetition_members_list list_repetition_members_v1 Exact class veya repeat family üyeleri public Cursor
alignment_targets_list list_alignment_targets_v1 Dil çiftindeki görünür hedefler public Cursor
annotations_list list_annotations_v1 Owner’a bağlı yayınlanmış annotation projection’ları public Cursor
public_provenance_get get_public_provenance_v1 Disclosure-approved source/release özeti public Yok
curation_proposal_get get_curation_proposal_v1 Yetkili kullanıcının exact proposal kaydı authenticated/curator Yok
curation_proposal_submit submit_curation_proposal_v1 Immutable proposal payload’ını dondurup sunar eligible authenticated Yok
curation_review_record record_curation_review_v1 Frozen revision için review yazar eligible authenticated Yok
curation_decision_record record_curation_decision_v1 Review kümesine bağlı decision yazar eligible authenticated Yok
curation_decision_apply apply_curation_decision_v1 Accepted decision’ı atomik ve exact-once uygular eligible authenticated Yok

public burada anon, authenticated ve app_runtime rollerinin visibility filtresinden geçmiş aynı contract shape’ini ifade eder. Her görünür source, related entity’yi görünür yapmaz; her member ayrıca filtrelenir. Curation write operasyonları yalnız authenticated request role’üne grant edilir, fakat bu rol tek başına curation yetkisi değildir. Routine güncel actor capability, revocation, policy ve freeze epoch’u DB içinde yeniden doğrular; internal curation_executor public caller olmaz. İşlemler doğrudan tablo DML’i değildir ve idempotency_key zorunludur. Proposal düzeltmesi reviewed byte’ı değiştirmez; expected_parent_id bağlı yeni successor proposal olarak yeniden sunulur.

Bu ayrım machine contract’taki authorization_profile ile zorunludur. Public read release_visibility; curation read güncel subject/eligibility/revocation; curation write bunlara ek authority epoch, freeze epoch ve exact policy release kontrollerini aynı transaction’da yapar. Operation’daki caller role ile profile role’ü farklıysa contract yayımlanamaz.

Proposal submission, payload’ı DB’ye yazmadan önce curation-proposal-json-v1 ile canonicalize eder. Request; payload, action, target ve evidence inventory root’larıyla expected-state, authority-snapshot ve policy root’larını birlikte taşır. Persist edilen revision insert-only’dir; review proposal_revision_id + payload_root çiftine bağlanır. Değişiklik aynı row’u güncellemez, exact parent revision’a bağlı successor submission üretir.

Dispatch başarılı olduğunda transaction başında şu context tek tuple olarak pinlenir:

api_deployment_id
api_contract_release_id
api_operation_revision_id
code_release_id
schema_release_id
data_release_set_root
policy_release_set_root
exposure_manifest_root

Client bu parçaları bağımsız seçip geçerli olmayan bir bileşim üretemez. Historical pin yalnız accepted deployment compatibility kaydının exact tuple’ına eşitse kullanılır. Her continuation aynı context’i taşır; yeni release’e sessiz fallback yoktur.

Başarı envelope’u exact olarak şunları taşır:

{
"request_id": "uuid",
"operation_id": "entity_occurrences_list",
"result_state": "available",
"state_context": {
"reason_code": null,
"retry_after_ms": null,
"status_resource_id": null
},
"release_context": {},
"data": [],
"page": { "next_cursor": "opaque", "has_more": true }
}

page sayfalanmayan operation’da da shape drift yaratmamak için null olarak bulunur. Failure envelope’u request_id, operation_id, error ve release_context taşır. Release context yalnız dispatch öncesi reddedilen istekte null olabilir. Internal SQL, relation, policy expression, source locator, actor veya private evidence detail’i iki envelope’a da girmez.

Her operation data_cardinality: object | list bildirir. Cursor kullanan operation’ın data alanı daima response item type’ının array’idir; diğerleri object’tir. Optionality, envelope alan tipleri ve nested public projection type’ları machine contract’ta explicit type spec olarak bulunur. Generator singular response_type adından cardinality tahmin etmez. Envelope’daki operation_response_data serbest generic veya JSON değildir; operation’ın exact response_type ve data_cardinality alanlarından türetilir, nullability’si state payload contract’ına ve transitive public type closure’a bağlıdır.

HTTP başarı, domain sonucunun available olduğu anlamına gelmez. Başarılı operation aşağıdaki discriminated state’lerden tam birini döndürür:

Result state Data kuralı UI state Anlam
available Dolu typed data content İstenen görünür projection hazır
empty Typed boş değer empty Sorgu tamamlandı ve görünür üye yok
pending Data yok pending İş devam ediyor; retry süresi veya status resource zorunlu
unavailable Data yok unavailable Bu istek için terminal; capability bu tuple/policy altında sunulamıyor
ambiguous Görünür typed target kümesi ambiguous Tek hedef iddiası yapılamıyor
retired Lifecycle summary retired Exact kayıt var, current kullanım dışı; sessiz redirect yok

loading server state değildir. Yalnız tamamlanmamış yerel request’in geçici UI durumudur. Her terminal response loading’i bitirir. Böylece boş anlam, unavailable annotation veya bir RPC hatası sonsuz “yükleniyor” görünümüne dönüşmez. pending cevabı da mevcut network loading’ini bitirir ve ayrı, bounded polling state’ine geçirir; retry süresi veya status resource olmadan pending üretilemez.

state_context, reason_code, retry_after_ms ve status_resource_id alanlarını her zaman aynı shape’te taşır. pending en az retry süresi veya status resource vermek zorundadır; non-retryable state retry_after_ms veremez.

Machine contract bu yapıyı kapalı discriminated union olarak tanımlar. empty yalnız cursor listelerinde zero-length typed array; ambiguous en az iki görünür candidate’ın operation’a özgü path’i; retired ise operation’a özgü lifecycle path’i ile geçerlidir. Bilinmeyen state contract violation ve release blocker’dır; generated TypeScript switch never exhaustiveness kontrolünü geçmek zorundadır.

Error envelope code, retryable, detail_code ve retry_after_ms alanlarından oluşur. Kapalı public code kümesi:

Code HTTP Client state Not
invalid_request 400 error Yalnız validation code görünür
unauthenticated 401 signed_out Kimlik detail’i yok
not_found_or_forbidden 404 not_found Yokluk ve yasak public caller için ayırt edilemez
unsupported_contract 406 update_required Yalnız desteklenen major metadata’sı
conflict 409 conflict Curation/concurrency code’u, private detail yok
continuation_invalid 409 refresh_required Cursor nedeninin ayrıntısı açıklanmaz
response_budget_exceeded 422 unavailable Complete projection contract limitine sığmadı; partial body yok
rate_limited 429 retrying Yalnız retry süresi
integrity_failure 503 unavailable Yalnız public incident ID
service_unavailable 503 unavailable Retry policy’si taşır
timeout 504 retrying Bounded timeout; geniş sorguya fallback yok

Public API’de ikinci bir 404 code’u eklenemez. Restricted audit gerçek absent/forbidden sebebini ayrı private evidence olarak tutabilir; public body, count, timing veya cursor bu ayrımı sızdıramaz.

Bütün listeler forward-only, opaque ve authenticated cursor kullanır. Default page 50, üst sınır 100, default total count yoktur. Her list operation kendi response item’ında bulunan UUID order_keys dizisini bildirir: occurrence için sentence_id, occurrence_id, repetition için member_id, alignment için target_unit_id, alignment_group_id, annotation için annotation_id. Gizli storage alanı cursor order’ına sokulamaz.

Cursor’ın doğrulanmış içeriği şunlara bağlıdır:

  • exact deployment, contract ve operation revision;
  • exact data ve policy release set root’ları;
  • canonical query shape hash’i;
  • authorization subject ve visibility scope hash’leri;
  • son order key, page size ve expiry.

Cursor en fazla 15 dakika geçerlidir. Signature, operation, release, query, principal, visibility veya page-size eşleşmezse continuation_invalid döner. Cursor içeriği public authority değildir ve client tarafından düzenlenemez. Bir kullanıcıya ait continuation başka kullanıcı, policy veya yeni deployment altında kullanılamaz.

Canonical üretim yönü tektir:

accepted API contract + exposure_manifest.public_surface
-> accepted OpenAPI projection
-> TypeScript API types + typed operation client

Her generated artifact generator ID/version, API contract root, exposure manifest root ve OpenAPI root header’ı taşır. Elle değişiklik, private dependency type’ının output’a girmesi veya yeniden üretimde byte drift release blocker’dır. TypeScript union’ları result_state ve error code için exhaustive olmalıdır; default ile bilinmeyen state’i empty ya da unavailable saymak yasaktır.

Nested type reference’ları yalnız machine contract’taki public_type_dependencies allowlist’inden gelir ve her biri exposure_manifest.public_surface üyeliğini kanıtlar. core.*, provenance.* gibi storage çağrışımlı namespace veya unconstrained json_object transitive closure’a giremez. Entity, annotation ve curation payload’ları da isimlendirilmiş public projection schema’larıdır; private alan sonradan filtrelenmez.

Bütçe bir internet latency SLA’sı değil, aynı fixture ve deployment profile’ında ölçülen DB/reponse release kapısıdır. Operation daha pahalı davranacaksa gizli biçimde limiti artırmak yerine contract/fixture review gerekir.

Profil Max item Max body Max DB statement DB p95 hedefi Statement timeout
point_read 1 256 KiB 8 100 ms 1,000 ms
chapter_read 50,000 16 MiB 12 350 ms 2,500 ms
bounded_list 100 512 KiB 8 150 ms 1,500 ms
graph_list 100 1 MiB 12 250 ms 2,000 ms
curation_write 1 256 KiB 20 500 ms 3,000 ms

Chapter bütçesindeki item sayısı public pagination anlamına gelmez; bir accepted chapter projection içindeki bounded structural unit tavanıdır. Limit aşılırsa partial chapter döndürülmez: serialization başlamadan response_budget_exceeded üretilir. Core reconstruction invariant’ını koruyan yeni contract revision veya stream/package taşıma stratejisi gerekir. Machine budget_enforcement bütün response’u ölçer ve chapter’ı all_or_nothing olarak işaretler.

F8-003 corpus üst sınırı incelemesinde accepted kaynak kümesinin en büyük chapter’ı 40.314 token occurrence, 3.313 sentence ve 1.042 paragraph olarak ölçülmüştür. Bütçe bu üç array’in toplamı olan 44.669 structural item’ı sayar. Public projection’ın UUID ve bütünlük alanlarıyla tahmini boyutu 11.351.169 bayttır; eski 25.000 / 4 MiB sınırı bu ve başka büyük chapter’ları eksiksiz reader kapsamının dışında bırakıyordu. 50.000 / 16 MiB sınırı mevcut corpus üst sınırına açık marj ekler. Response shape, hata anlamı ve all-or-nothing davranışı değişmediği için bu genişletme aynı major içinde compatible capability release’idir; eski deployment kendi eski bütçesiyle exact olarak pinlenebilir. Bu pin yalnız rakamları değil sayım semantiğini de korur: v1.0 limiti yalnız token occurrence sayar, v1.1 ise paragraph + sentence + token occurrence toplamını structural item olarak sayar. F8-015 ölçüm sonucunda daha verimli bir taşıma gerekirse bu, mevcut chapter’ı sessizce bölmeden yeni bir contract revision olarak ele alınacaktır.

N+1, limitsiz recursive traversal, exact count için tam corpus scan ve timeout sonrası daha geniş query fallback’i yasaktır. Explain/statement count fixture’ları private schema detayını public response’a taşımadan release evidence’ına yazılır.

Contract diff aşağıdaki olgulara mekanik çevrilir:

  • yeni operation veya optional response alanı: additive_optional_contract_behavior;
  • required alan ekleme, alan kaldırma, type/cardinality veya result/error anlamı değiştirme: required_public_contract_behavior_removed_or_reinterpreted;
  • authorization zayıflatma: authorization_or_disclosure_weakened_incompatibly;
  • desteklenen tuple’ı sınır/successor olmadan kaldırma: supported_release_tuple_invalidated_without_successor_or_major_boundary.

Bu projection MAT-002 evaluator’ına gider. “Generated diff küçük” veya “yalnız RPC rename” compatibility class’ını düşürmez.

Bir operation yalnız machine contract’ta rezerve edilmiş, fakat önceki hiçbir accepted contract release’inde operation revision ve executable deployment kazanmamışsa henüz desteklenen response davranışı oluşturmaz. İlk activation paketi request/response şeklini düzeltebilir ve bunu aynı major içindeki ilk capability release’i olarak yayımlayabilir; bunun kanıtı predecessor deployment envanteri ve bağımsız schema review’udur. İlk accepted operation revision’dan sonra bu istisna biter ve yukarıdaki normal type/cardinality compatibility kuralları eksiksiz uygulanır.

  • F3-019 machine contract’taki exposed schema, operation revision, manifest ve release context alanlarını migration ve FK/check’lere dönüştürür.
  • F4/F5/F6/F7 domain projection’ları yalnız bu operation type boundary’leri üzerinden public olur; yeni storage relation public endpoint yaratmaz.
  • F8 generated OpenAPI/TypeScript artifact’larını, exhaustive query state reducer’ını, cursor yenilemesini ve bütçe telemetry fixture’larını uygular.
  • F10 deployment tuple, exposure manifest, contract diff ve performance evidence’ını release authorization’a bağlar.

Uygulama testleri en az aynı operation’ın iki principal’la cursor reuse reddini, sayfa arasında release değişimini, empty/pending/unavailable ayrımını, forbidden/absent public eşdeğerliğini, generated type drift’ini, bounded chapter reconstruction’ı ve her bütçe profilinin fixture ölçümünü kanıtlamalıdır.

  • DB, web ve dokümantasyon aynı operation/state adlarını kullanır.
  • Frontend eksik data’yı sonsuz loading veya yanlış empty state olarak yorumlamaz.
  • “Geçtiği yerler”, tekrar ve multilingual listeleri release/policy değişiminde karışmaz.
  • Storage refactor public JSON’u istemeden değiştiremez.
  • Performans beklentileri ancak production sorunu çıktıktan sonra değil release öncesinde ölçülebilir hale gelir.
  • Yeni public capability operation allowlist ve generated contract release’i ister.
  • Cursor key rotation ve historical deployment doğrulaması server-side state gerektirir.
  • Role-matrix, state, pagination ve budget fixture’ları sıradan endpoint testinden daha geniştir.

Bu maliyetler accepted identity ve reference ağının sessiz API drift’ine uğramasını engelleyen zorunlu sınırdır; runtime’a gereksiz domain soyutlaması eklemez.