İçeriğe geç

Kararlar

Tasarım belgesi design/spec.md’dir; uygulama sırasında alınan kararlar design/decisions.md’de tutulur ve çelişkide o dosya geçerlidir. Tasarım aşamasının karar kaydı (Ek A, A53–A76) ve senaryo matrisi (Ek B) özgün tasarım belgesindedir; senaryoların her biri Tarifler sayfasında çalışan bir testtir.

Aşağıdaki tablo derleme anında design/decisions.md’den okunur; dosyaya eklenen her karar burada görünür.

#KararGerekçe
A77Monorepo: bun workspaces, docs: Astro Starlight → GitHub Pages, paket: npm. §10'daki pnpm / Cloudflare Pages yerine D7 geçerliD7 kapandı
A78package.json exports'a ./calendar eklendiA72 modülü getiriyor, §10 listesi eksikti
A79graduated için "artmayan fiyat" kuralı kaldırıldıB3 ([0: 0, 500: p]) artan fiyatlı; kural B3'ü imkânsız kılıyordu
A80Hold üst sınırı her modelde: en pahalı kademe (price/per) × adet (package: ceil(q/size) blok) + tüm flatMicroUsd + (volume on_crossing) usedSoFar × (pmax − pmin); perObservation.max ile kırpılırA79 sonrası "ilk kademe" garantisi yok; en kötü durum her konumda geçerli (özellik testi)
A81Rated.totalMicroUsd hacim düzeltmesini içermez; düzeltme adjustmentMicroUsd, ayrı satır …:adjUsage satırı = gözlemin kendi tutarı; düzeltme ayrı idempotent kalem
A82usedSoFar zorunluluğu: çok kademe, volume, package, herhangi bir flatMicroUsd, ya da cumulative (carry yoksa). Tek kademeli sabit ücret de ilk girişi bilmek zorunda"Sessiz 0 yok" (A58)
A83cumulative: usedSoFar ile floor(T(u+q)) − floor(T(u)) (tam telescoping); carry verilirse floor(carry + Δ). Hacim none modelinde T tanımsız → carry gerekirSapmasızlık özelliği T üzerinden kanıtlanıyor
A84Aynı çağrıda aynı (meter, dims) satırları birbirinin usedSoFar'ını ilerletir; aynı sayaç tekrar ederse refId #2, #3 eki alırÇok satırlı gözlem tek tutarlı gözlem; refId çakışması veri kaybı olurdu
A85Sıfır tutarlı satır usage'a yazılır, ledger'a yazılmaz"sayılır, 0 yazılır" + boş ledger kalemi yok
A86Yeni hata nedenleri: invalid_ref, hold_exceeded, hold_closedRef şeması doğrulanıyor; hold değer nesnesi olduğundan kapanmış/aşılmış hold yakalanmalı
A87Hold değer nesnesi her çağrıda güncellenmiş hâliyle döner (result.hold); capture sıra numarası refId'ye girer (…:capture:n)Değer nesnesi + idempotensi: aynı eski hold ile retry aynı refId'yi üretir
A88Kısmi capture'lar teleskopik: R(u, c+q) − R(u, c); hold tek gözlem gibi yuvarlanırAksi hâlde per_event_up parçalı capture'da üst sınırı aşar
A89Havuz sayaçları boyutsuzdur; havuz satırı dims: {}Besleyen boyutlarından havuz boyutu türetilemez
A90Havuz miktarı ceil(q × w) float gürültüsünden arındırılır (12 hane)10 × 1.1 12 değil 11 olmalı
A91plan.observe sonucu lines (fiyatlanmış satırlar, havuzlar dahil) döner; metering.price(lines, …, { pools }) bunları tekrar fiyatlarDO içinde gömülü price() (§9) için satırların tarifesi gerekir
A92metering.commit(fn) adapter bağlar, metering.commit(plan) plan yazar (argüman türüne göre)§7 metering.commit(plan) kullanıyor
A93Tip düzeyinde durum: sayaçlar MeterEntry interface birliği; meter() dönüş tipi alias'sız yazılırAlias önceki durumu alias argümanı olarak taşıyor, ~100 sayaçta TS2589. 500 sayaç: ~0,6 sn tip kontrolü (havuzlar artımlı tutulur)
A94getRate callback'i dört parametreyi de yazmalı (_ctx, _at)TS birlik-tuple rest parametresinde eksik parametreli callback'i reddediyor
A95typed<T>() işaretçisi: .meter("x", typed<{…}>()), .context(typed<C>()) ya da .context<C>()Kısmi generic çıkarımı yok; düz tip için değer düzeyi işaretçi
A96Pratik zincir sınırı ~800 .meter(): TS parser'ı 1000 zincirli çağrıda yığını taşırıyorKatalog bölünebilir; hedef 500
A97B22 (işlem başı sabit): her işlem kendi dönemidir → getRate usedSoFar: 0 döner (tierPeriod: "observation")flatMicroUsd "kademeye ilk giriş"; işlem başı ücret için her gözlem yeni dönem
A98Dönem sonu gözlemi at: window.to − 1 ile yapılır; tarifesi döneme bağlı (per = ay saniyesi) getRate at'e bakar ve window.to bir sonraki dönemi seçerPencereler yarı açık [from, to) (/calendar, /gauge)
A99Aynı (refType, refId) ile tekrar edilen çağrı ok ve yeniden hesaplanan charged döner; commit hiçbir şey yazmaz. Sonuç "bu çağrının fiyatı"dır, "yazılan tutar" değilcommit imzası void; yazıldı/yazılmadı bilgisini taşımak adapter kontratını büyütür. Gerekirse ileride commit { duplicate: true } dönebilir
A100Sayaç (usedSoFar) kapsamı uygulamanındır: bellek adapter'ı ve referans adapter'lar sayaç başına (boyutsuz) sayar; boyut başına kademe isteyen getRate'te dims ile sayar (sqliteAdapter.usedSoFar({ dims }))"Bu dönemde bu sayaçta" tanımı (§2) boyutları kapsamaz; boyut başına kademe bir tarif seçimidir
A101Bir hold'da her (meter, dims) için tek satır vardır: tekrar eden satırlar birleşir, havuz satırı besleyicilerini feeders[] olarak taşır; extend mevcut satırı büyütür (tarifesi ve konumu sabit). Hold her anahtarı bir kez yuvarlar; cumulative'de ayrı satırlı observe ile birebir aynıdır, per_event_up'ta yalnızca yuvarlama kadar (satır başına en fazla ücret + düzeltme = 2 mikro birim) farklıdır, yönü garanti değildirAynı anahtarlı ayrı satırlar extend'de kademe konumlarını çakıştırıyordu (inceleme #1) ve capture edilemiyordu (#6)
A102Adapter'lar hold başına gerçekten ayrılan tutarı tutar: release onu serbest bırakır (plan tutarı değil), capture rezervasyonu en fazla onu kadar düşürürFarklı tutarla tekrarlanan hold no-op yazılır ama yeniden hesaplanmış upperBound döner; plan tutarına güvenen release rezervasyonu kalıcı kaydırıyordu (#5)
A103minimumCommit refId'si hesabı içerir: minimum_commit:{account}:{period}Ledger tekilliği (type, refType, refId) hesaplar arası geneldir; ikinci hesabın kalemi sessizce düşüyordu (#8)
A104Float gürültüsü yalnızca tamsayıya göreli 1e-9 yakınsa temizlenir (ceilClean); carry toplamı sonraki tamsayıya 1e-9 yakınsa yuvarlanır; aynı anahtarlı satırlar carry'yi zincirler12 hanelik kesme büyük tam değerleri bozuyordu; ⅓+⅓+⅓ 1 etmiyordu; ikinci satır ilk satırın carry girdisini tekrar kullanıyordu (#3, #10, #11)
A105SQLite ve Cloudflare referans adapter'ları ayrı paket değil, pricemeter/sqlite ve pricemeter/cloudflare alt yollarıdır (§10'daki packages/sqlite, packages/cloudflare yerine)İkisinin de bağımlılığı yok (sürücü ve Workers tipleri yapısal); tek paket, tek sürüm, tree-shake
A106volume + cumulative + on_crossing olmayan tarife carry olmadan usage_required döner (requiresCarry)Tanımlı bir koşu toplamı yok; carrysız her alt-mikro tutar kayboluyor ve sonsuza dek 0 kesiliyordu (A58 "sessiz 0 yok")
A107Kota: getRate { rate, usedSoFar, limit? } döner; aşan çağrı quota_exceeded (cause: { meter, limit, used, requested }), hiçbir şey yazılmaz. limit: 0 = planda yok. Limit sayaç başınadır (boyutlar toplanır), dönemi usedSoFar'ınkidir. Limit usage satırına yazılır; adapter commit içinde atomik kontrol eder ve QuotaExceeded fırlatır (SQLite: windowOf, DO: periodOf). Hold/extend ayrılan miktarı kontrol eder; capture asla reddedilmez"Ne kadar"ın sınırı: fiyatı olan ama hakkı olmayan kullanım. Bakiye kapısıyla aynı yarış, aynı çözüm (atomik sınır). Rate'e girmedi: ücretsiz sayaçlarda da gerekir ve fiyat modeline bir şey eklemez
A108Koltuk gibi inip çıkan seviyelerin limiti uygulamadadır (examples/saas-seats/src/seats.ts); limit yalnızca artan olay sayaçları içindirSayaçlar yalnızca artar; koltuk silinip yeniden eklenebilir
# Soru Karar
D7 Docs alan adı ve erişim bun workspaces monorepo (paket + docsite); docsite Astro, GitHub Pages; paket npm
D16 Takvim yardımcıları /calendar modülü (A72)
D17 Bağlam doğrulaması her çağrı (A73)
D18 feeds fonksiyonu ctx görsün mü evet (A74)
  • Sessiz 0 yok (A58, A82): usedSoFar gerektiren bir tarifede eksik sayaç usage_required döner; kütüphane asla 0 varsaymaz.
  • Değer nesnesi (A87, A101): hold her çağrıda güncellenmiş hâliyle döner ve (meter, dims) başına tek satır taşır.
  • Tek paket (A105): SQLite ve Cloudflare adapter’ları pricemeter/sqlite ve pricemeter/cloudflare alt yollarıdır; bağımlılıkları yoktur.
  • Sonuç = bu çağrının fiyatı (A99): tekrarlanan çağrı yeniden hesaplanan charged’ı döner; yazılıp yazılmadığını commit bilir.
  • Sayaç kapsamı uygulamanın (A100): referans adapter’lar sayaç başına sayar; boyut başına kademe bir getRate seçimidir.