@appaflytech/zeyrek-ai-mcp 0.0.50 → 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -112,6 +112,7 @@ dalından devam eder.
112
112
  | `resetForm(name)` | Formu sıfırlar: alanlar başlangıç değerine döner, form "gönderilmemiş" olur, doğrulama mesajları silinir. Kayıt sonrası "yeni kayıt" için alanları tek tek `null`'a çekme — gönderilmiş formda her biri "zorunlu alan" mesajı çıkarır. Ad ya da komponent değişkeni alır: `resetForm('risk_form')` / `resetForm(risk_form)`. **Yalnız UI flow kodunda** |
113
113
  | `LocalStorage` · `Widget` · `params` | yerel veri · widget bağlamı · olay parametreleri. `LocalStorage` barındırıcının deposudur, salt okunur ve değerler METİNdir: web'de uygulamanın kökenindeki `localStorage` (Studio önizlemesinde Studio'nun kendi anahtarları da görünür), cihazda akışın `storage` deposu. `storage` adımı düz bir anahtara yazınca `LocalStorage.<anahtar>` aynı akışta yeni değeri verir ve sayfanın bağları yeniden değerlendirilir (`isSelected: {{ LocalStorage['zeyrek:theme-mode'] === 'dark' }}`). `state.page\|app.<key>`'e yazan adım `LocalStorage`'a girmez (ARC-147, 2026-09-29) |
114
114
  | `Device` | Salt okunur cihaz durumu, barındırıcı günceller: `Device.isOnline` — ağ bağlantısı var mı (bilinmiyorsa `true`). Değişince sayfanın bütün bağları yeniden değerlendirilir; çevrimdışı bandı: `isHidden: {{ Device.isOnline }}`. Cihazda `expo-network`, web'de tarayıcının `online`/`offline` olayları. `Device.biometric = { available, enrolled, kinds }` (kök ADR 0021): `available` cihaz kullanıcıyı şu an doğrulayabilir (biyometri **ya da** cihaz parolası kurulu), `enrolled` biyometri kayıtlı, `kinds` ⊆ `face · fingerprint · iris`; barındırıcı vermezse (web, derleme taşımıyor) `{ available: false, enrolled: false, kinds: [] }`, Studio önizlemesi ve kanvas senaryosu `{ true, true, ['face'] }`. Uygulama ön plana dönünce yeniden okunur. Ağ yokken okumaların ne döndüğü (`Steps.<ad>.cache.at`, `error.network`): `platform_guide('page')` §Kural 1g-4. `Device.offline` (yalnız cihazda): `{ ready, total }` — uygulamanın kaç ekranı çevrimdışı açılmaya hazır; paket inmeden ve web/önizlemede yoktur (`Device.offline?.ready`); servis yazması kuyruğu varsa `queue: { pending, rejected }` ve `rejected` (reddedilen kayıtlar) da buradadır. `Device` adında komponent ifade bağlamından süzülür (ayrılmış ad) |
115
+ | `Entitlements` · `Products` · `Balances` | Uygulama içi satın almanın salt okunur durumu (kök ADR 0027), barındırıcı yazar: `Balances.<bakiye>` sayı — tüketilen ürünün (kredi, jeton) güncel bakiyesi, sunucudan gelir ve harcama/satın alma sonrası kendiliğinden güncellenir (bilinmeyen bakiye `undefined`: `{{ Balances.kredi ?? 0 }}`; iade sonrası negatif olabilir); `balance-spend` adımının `args.value`'sundaki düz ad katalogda tüketilen ürünün `balance`'ı olmalıdır (yoksa yazım `FLOW-046` ile durur). Hak tarafı: `Entitlements.<hak>` boolean — hak açık mı (bilinmeyen hak `false`; ekran ürüne değil hakka bakar: `isHidden: {{ !Entitlements.premium }}`); `Products.<anahtar> = { price, period, trial }` — mağazanın yerelleştirilmiş fiyat metni (`₺949,99`), süre ve deneme ISO 8601 (`P1Y`, `P7D`) ya da `null`; `purchase.value`'ya yazılan düz anahtar katalogda olmalıdır (yoksa yazım `FLOW-045` ile durur), seçimden gelen anahtar `{{ … }}` şablonudur ve yazımda denetlenmez. Değişince bağlar yeniden okunur (satın alma ve geri yükleme sonrası kendiliğinden). Studio önizlemesi ve oynatıcı katalogdan deneme değerleri verir; web uygulamasında ikisi de boş nesnedir. `Device`'ın altında değil, kendi adlarıyla. Tarif: `platform_guide('mobile-steps')` §Satın alma tarifi |
115
116
  | **Ayrılmış adlar** | Bu tablodaki globallerle (ve önsözün iç adlarıyla: `payload`, `error`, `repeaterId`, `_` ile başlayanlar…) aynı `name`'i taşıyan komponent (runtime yalnız İLK `-`'yi siler: `ite-m` da `item` sayılır) ifadede kendi adıyla GÖRÜNMEZ — süzülür, uyarı loglanır (liste: `@ng-apps/flow` `EXPRESSION_PRELUDE_NAMES`). Eskiden aynı ad sayfanın BÜTÜN ifadelerini `SyntaxError`'la düşürürdü (ARC-143). Gateway artık bu adların HEPSİNİ **PAGE-009** ile sayfa/layout yazma yollarında ve `page_schema_validate`'te istek gitmeden reddeder — komponente `t`, `state`, `event`, `item`, `self`, `Page`, `Row` gibi ad verme |
116
117
  | `t(key)` | Çeviri anahtarını çözer: aktif dil → projenin varsayılan dili → anahtarın kendisi. Boş string de "eksik" sayılır. Sözlük `machine.context.state.translations`'tan gelir; dil `language-selector` ile değişince bir sonraki çağrı yeni dili görür. **Yalnız UI flow kodunda vardır** — backend ifadelerinde karşılığı `Loc()`'tur (`platform_guide('workflow')`) | `t('home.welcome')` |
117
118
  | `log(...)` | console (proxy'leri temizler) |
@@ -284,6 +284,69 @@ birleşir (`{ appLock: { timeoutSeconds: 300 } }` açık/kapalıyı korur).
284
284
  görüntüsü örtülür, Android'de örtülmez. **Oynatıcıda (Expo Go) ve Studio önizlemesinde kilit yoktur** — tek bir
285
285
  ekranı kapı arkasına almak için `biometric-auth` adımı (platform_guide('mobile-steps')).
286
286
 
287
+ ## Satın almalar
288
+
289
+ Uygulama içi satın alma (kök ADR 0027): abonelik (`subscription`), tek seferlik kalıcı ürün (`non_consumable`) ve tüketilen
290
+ ürün (`consumable` — kredi, jeton). Ürün Zeyrek anahtarıyla anılır, mağaza kimliği sayfaya yazılmaz; hak (`entitlement`) Apple'da
291
+ abonelik grubu, Google'da abonelik, abonelik ürünü Google'da temel plandır (anahtar, `_` → `-`). Sıra:
292
+
293
+ 1. **Ürün:** `mobile_product_upsert` (anahtar, tür, hak, TRY fiyatı, abonelikte süre ve deneme, dil başına ad ≤ 30 ve
294
+ açıklama ≤ 45) — `mobile_product_list` mağaza başına durumu gösterir. Mağazaya gitmiş ürünün anahtarı, türü, hakkı ve
295
+ bakiye adı kilitlidir; ad/fiyat/süre/miktar değişikliği `pendingChanges`'e düşer (miktar mağaza alanı değildir, doğrudan
296
+ geçer). **Tüketilen ürün:** `kind: 'consumable'`, hak yerine `balance` (bakiyenin adı, ekranlar `Balances.<ad>` okur) ve
297
+ `grantsAmount` (her satın almada eklenen miktar, 1–1.000.000); süre, deneme ve hak yasak. Apple'da `CONSUMABLE`, Google'da
298
+ tek seferlik tüketilebilir ürün — grup, temel plan ve deneme adımı yoktur, gönderim yolu `non_consumable` ile aynıdır.
299
+ Aynı `balance`'ı birden çok ürün paylaşır. Kullanıcı ürünü her istediğinde yeniden alır; mağaza **geri yüklemez** (K9):
300
+ girişsiz uygulamada telefon değişince bakiye gider, girişli uygulamada hesaba taşınır (`MOB-036` kredi harcama akışı
301
+ yazılırken girişsiz uygulamayı uyarır). İade o satırın miktarını bakiyeden geri alır, bakiye negatife düşebilir.
302
+ 2. **Hazırlık:** `mobile_purchase_readiness` — iOS: Apple hesabı, ücretli uygulama sözleşmesi, satın alma anahtarı
303
+ (`.p8`), bildirim adresi; Android: Play hizmet hesabı, ödeme profili, faturalandırmalı derleme, hizmet hesabı izni,
304
+ gerçek zamanlı bildirim konusu. `manual` satırları konsolda insan yapar; satır `hint`'i nerede olduğunu söyler.
305
+ 3. **Faturalandırmalı derleme (Android):** Google ürün açmayı `BILLING` izinli bir derleme yüklenmeden reddeder
306
+ (`play_billing_build`). Ürünlü uygulamanın derlemesi `purchases` bloğunu ve izni taşır — kabuk 0.9.0+ (ön kontrol
307
+ P-23: `purchases_shell_outdated` eski kabuk, `purchases_unknown_product` bir `purchase` adımının düz anahtarı
308
+ katalogda yok, `purchases_not_sent` ürün mağazaya gitmemiş); `mobile_build_start
309
+ { platform: 'android', target: 'internal' }`. Ürünü olmayan uygulamanın ikilisinde satın alma modülü ve izin yoktur.
310
+ 4. **Gönder:** `mobile_product_send` (202, asenkron) → `mobile_product_send_status`. Geri alınamaz: mağazadaki ürün API'den
311
+ silinmez, Apple ürün kimliğini yeniden kullandırmaz — anahtar ve fiyatı kullanıcıya onaylatın. Fiyat değişiminde
312
+ mevcut aboneler varsayılan olarak eski fiyatta kalır (`applyToExisting: true` onlara da uygular). Düşen adım
313
+ (`paid_agreement`, `play_permission`, `play_billing_build`) giderilince `mobile_product_send { retrySendId }`.
314
+ 5. **Ekran:** `purchase` / `restore-purchases` adımları ve `Entitlements` (`platform_guide('mobile-steps')` §Satın alma
315
+ tarifi); tüketilen ürün için `balance-spend` adımı ve `Balances` (§Kredi harcama tarifi).
316
+
317
+ **İnsanın işi (MCP yapmaz):** satın alma anahtarını (`.p8` + Key ID + Issuer ID) Studio › Satın almalar › Mağaza
318
+ hazırlığı'nda yüklemek (gizli bilgi); App Store Connect'te ücretli uygulama sözleşmesi, banka ve vergi; bildirim adresini
319
+ (`notificationUrl` satırının `copyValue`'su) App Store Connect › App Information › App Store Server Notifications
320
+ (Version 2, production ve sandbox aynı adres) alanına yazmak; Play Console'da ödeme profili, hizmet hesabına ürün izni,
321
+ gerçek zamanlı bildirim Pub/Sub konusu ve kimlik doğrulamalı push aboneliği (`rtdnTopic` `copyValue`); App Review
322
+ inceleme görseli. Apple ilk satın alma ürününü uygulamanın yeni sürümüyle inceler: ürün `ready` kalır, sürümle
323
+ incelemeye gönderilince `in_review` olur. Satışlar `mobile_purchase_list` (gerçek satış `production`'dadır; TestFlight
324
+ ve Play iç test satın almaları `environment: test` etiketiyle, `includeTest: true` olmadan görünmez).
325
+
326
+ ## Mağazadan taşıma
327
+
328
+ Yayındaki bir uygulama (App Store / Google Play) Zeyrek'e **bir kez okunur, bir kez onaylanır**; sonra tek kaynak Zeyrek'tir
329
+ (kök ADR 0028). Yalnız ilk derlemeden önce (MOB-037).
330
+
331
+ 1. `mobile_store_import_start { applicationId, bundleId, packageName }` → bağlantı satırı ve okuma işi.
332
+ 2. `mobile_store_import_report` → `state: ready` olunca kovalar: **auto** (Zeyrek'e gelir) · **upload** (siz yüklersiniz:
333
+ ikon, 6.5" ekran görüntüsü) · **kept** (mağazada kalır: beyan içerikleri, ülke fiyatı, promosyon, tüketilen ürün).
334
+ 3. `mobile_store_import_mapping` → `scheme` (eski uygulamanın URL şeması, zorunlu), iki mağaza farkı
335
+ (`choice.<satır>`), ürün anahtarı/hak (`product.<i>.key|entitlement`), beyan onayı. Mağaza kimliği seçilmez (MOB-038).
336
+ 4. `mobile_store_import_apply` → tek işlem; mağazaya hiçbir şey gitmez. Örnek: `store-import-mapping`.
337
+
338
+ **Aboneler — üç kaynak:** eski liste (`mobile_purchase_import`, önce `dryRun` — MOB-039) · mağaza bildirimi (katalogdaki
339
+ ürünün tanınmayan işlemi **sahipsiz** satın alma açar) · cihaz (kabuk ilk açılışta sürüm başına bir kez sessiz geri
340
+ yükler, kabuk ≥ 0.10.0). Sahipsiz satır `mobile_purchase_list { owner: 'ownerless' }` ile görünür; geri yükleme kuruluma,
341
+ giriş hesaba bağlar.
342
+
343
+ **Apple bildirimleri:** `mobile_store_notification_forwarding { action: 'switch', confirm: true }` adresi Zeyrek'e çevirir,
344
+ eski adresi 90 gün iletir (MOB-040). Google'da Pub/Sub konusuna ikinci abonelik insanın işidir.
345
+
346
+ **Yayın öncesi:** ön kontrol P-24…P-31 (kimlik, sürüm, derleme numarası, upload anahtarı, ürünler, kabuk, bildirim, URL
347
+ şeması). Yayından hemen önce `mobile_store_diff { action: 'read' }`; `store_changed` / `both_changed` satırları
348
+ `choose` ile seçilmeden mağaza yayını başlamaz (MOB-041). Yayın yalnız `send` listesindeki alanları yazar.
349
+
287
350
  ## Hata kodları
288
351
 
289
352
  `mobile_build_status` → `error.code`.
@@ -30,6 +30,9 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
30
30
  | `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
31
31
  | `biometric-auth` | kullanıcıyı Face ID / parmak izi (yedekle cihaz parolası) ile doğrular (kök ADR 0021). `args.title` sistem penceresindeki gerekçe, `args.cancelText` vazgeç düğmesi — ikisi metin şablonu, boşsa uygulama dilindeki varsayılan; `args.value` yedek, düz metin: `device` (biyometri olmazsa cihaz parolası; boşsa bu) ya da `none` (`FLOW-042`; `fallback` / `reason` / `cancelLabel` anahtarı sessizce düşer). Başarı: `Steps.<adım>.data = { verified: true }` → `always`. **Vazgeçmek hatadır**: `error.status` `cancelled` · `not-enrolled` · `lockout` · `failed` · `unavailable` (donanım yok ya da derleme taşımıyor) · `unsupported` (web uygulaması, production `/view`) · `invalid`; `error` dalı → hata akışı → **o da yoksa akış bu adımda biter**, `always`'e bağlı adım koşmaz. Yerel bir kapıdır, sunucu için kanıt değildir. Paletten yalnız mobilde |
32
32
  | `scan-code` | tam ekran kod tarayıcı (kök ADR 0017): tek kod okur ve kapanır. `args.value` izinli türler, **virgüllü düz metin** (`FLOW-042`): `aztec` · `codabar` · `code39` · `code93` · `code128` · `datamatrix` · `ean8` · `ean13` · `itf14` · `pdf417` · `qr` · `upc_e`; boş → hepsi. `upc_a` yok: UPC-A etiketi `ean13` ve on üç haneyle (başında `0`) döner. `args.title` ekrandaki yönerge, metin şablonu (`{{ t('anahtar') }}`). Sonuç `Steps.<adım>.data`: `{ value, type, source }` — `source` `camera` ya da `manual` (ekrandaki "elle gir"; tür bilinmiyorsa `type: null`). `error` dalı: `status` `cancelled` (kullanıcı kapattı — **vazgeçme bir hatadır, `error` dalını bağla**) · `denied` (kamera izni yok) · `unavailable` (derleme kamerayı taşımıyor ya da tarayıcı zaten açık) · `unsupported` (web uygulaması) · `invalid`. Kamera izni kendiliğinden derlemeye girer (adım production'daysa). Önizleme (`/dev`, `page_screenshot`) kamera açmaz, "kod gir" penceresi çizer: `__scan-value`'ya yaz, `__scan-confirm` ya da `__scan-cancel` (çok türde `__scan-type-<tür>`). `page_screenshot` pencereyi yalnız `captureUnready: true` ile sürer (akış pencereyi beklerken "koşuyor" sayılır; her eylem 15 sn bekler). Tam gövde: `ngapps://examples/mobile-scan-code` |
33
+ | `purchase` | uygulama içi satın alma (kök ADR 0027). `args.value` ürünün Zeyrek **anahtarı**, metin şablonu: düz `premium_yillik` ya da `{{ plan_secim.value }}` (`FLOW-045`: katalogda olmayan düz anahtar yazılmaz; `{{ }}` dışındaki ifade harfi harfine anahtar sayılır, `productKey`/`sku` anahtarı sessizce düşer). Kabuk mağazanın penceresini açar, sunucu makbuzu mağazaya sorar, hak açılır: `Steps.<adım>.data = { productKey, entitlements }` → `always`; `Entitlements` kendiliğinden güncellenir. **Kapıdır**: `error.status` `cancelled` (vazgeçti) · `pending` (Ask to Buy, bekleyen ödeme ya da sunucuya ulaşılamadı — hak sonra kendiliğinden açılır) · `failed` · `unavailable` (mağaza yok, ürün satışta değil, ikili satın almayı taşımıyor) · `unsupported` (web uygulaması, production `/view`, kabuk < 0.9.0) · `invalid` (anahtar katalogda/manifestte yok); `error` dalı → hata akışı → **o da yoksa akış bu adımda biter**. Paletten yalnız mobilde |
34
+ | `restore-purchases` | mağaza hesabındaki satın almaları geri getirir (telefon değişti, uygulama yeniden kuruldu); argümansız. Başarı `Steps.<adım>.data = { entitlements }` — geri gelecek bir şey yoksa da başarıdır, sonucu hakka bakarak söyleyin. Hata sözlüğü `purchase`'la aynı ve o da kapıdır. Satın alma ekranında bir "Satın almaları geri yükle" düğmesi beklenir (`MOB-034`); yalnız tüketilen ürün satan ekranda beklenmez, tüketilen ürün geri yüklenmez |
35
+ | `balance-spend` | tüketilen ürünün bakiyesinden harcar (kredi, jeton; kök ADR 0027 bakiye bölümü). `args.value` bakiyenin **adı**, `args.title` **miktar** — ikisi de metin şablonu: düz `kredi` / `10` ya da `{{ adet.value }}` (`FLOW-046`: boş değer 422; katalogda tüketilen ürünün `balance`'ı olmayan düz ad yazılmaz; `{{ }}` dışındaki ifade harfi harfine metin sayılır; `balance`/`amount` anahtarı sessizce düşer). Harcama sunucuda atomiktir (aynı sahibin eşzamanlı harcaması sıralanır, bakiye aşılamaz). Başarı `Steps.<adım>.data = { balances }` → `always`; `Balances` kendiliğinden güncellenir, ayrıca okumayın. **Kapıdır**: `error.status` `insufficient` (bakiye yetmiyor — negatif bakiye de bu) · `unavailable` (çevrimdışı ya da sunucuya ulaşılamadı; çevrimdışı yazma kuyruğuna GİRMEZ) · `unsupported` (web uygulaması, production `/view`, kabuk satın almayı taşımıyor) · `invalid` (ad ya da miktar geçersiz; miktar 1 ve üstü tam sayı) · `failed`; `error` dalı → hata akışı → **o da yoksa akış bu adımda biter**. Başarılı her tıklama ayrı harcamadır; sonucu belirsiz kalan (`unavailable`) harcamanın anahtarı cihazda durur, aynı bakiye ve miktarla yeniden dokunuş iki kez düşmez. Anahtarı akışın verdiği harcama workflow `balance` adımındadır. Paletten yalnız mobilde |
33
36
 
34
37
  ## Bildirim tarifi
35
38
 
@@ -78,6 +81,54 @@ sürer: adımı tetikleyen tıklamanın raporu `pageState: 'awaiting-user'` der,
78
81
  'preview-biometric__verify' }` (Doğrulandı say) ya da `'preview-biometric__cancel'`. Tam gövde:
79
82
  `ngapps://examples/flow-biometric-confirm`.
80
83
 
84
+ ## Satın alma tarifi
85
+
86
+ **Satın alma tarifi** (kök ADR 0027): (1) ürünler kataloğa — `mobile_product_upsert { key: 'premium_yillik', kind:
87
+ 'subscription', entitlement: 'premium', priceTry, period: 'year', trial, texts }`; aynı hakkı veren aylık ve yıllık
88
+ abonelik mağazada bir gruptur. Hazırlık ve mağazaya gönderim `platform_guide('mobile-publish')` §Satın almalar. (2) Ekran
89
+ ürüne değil **hakka** bakar: teklif kökü `isHidden: {{ Entitlements.premium }}`, "Premium açıldı" kökü
90
+ `isHidden: {{ !Entitlements.premium }}` — ayrı sayfa gerekmez. Fiyatı yazmayın: `{{ Products.premium_yillik.price }}`
91
+ mağazanın yerel fiyat metnidir (`₺949,99`), `period` / `trial` ISO 8601 süre (`P1Y`, `P7D`) ya da `null`; plan seçimi
92
+ `choice-chips` ise `{{ Products[plan_secim.value]?.price ?? '' }}`. (3) Devam et düğmesinin akışı `start` → `purchase`
93
+ `{ value: '{{ plan_secim.value }}' }`; `always` teşekkür, `error` dalında `condition`: `pending` → "onay bekleniyor",
94
+ `cancelled` dışı → hata iletisi, `else` (vazgeçti) → hiçbir şey. (4) "Satın almaları geri yükle" düğmesi → `restore-purchases`
95
+ — bu akışı satın alma akışından ÖNCE yazın: `MOB-034` ekranın geri yükleme yolunu yalnız satın alma akışı yazılırken arar. (5) Abonelik satan ekranda süre ve fiyat, otomatik yenileme ve iptal yolu metni, kullanım koşulları ve
96
+ gizlilik bağlantısı (`href` taşıyan düğme; adres `mobile_store_get` `urls.privacyPolicy`) — App Store 3.1.2 ve Play
97
+ politikası ister, yoksa ekran incelemeden döner (`MOB-042`). Tam gövde: `ngapps://examples/flow-purchase-paywall`.
98
+
99
+ Tuzaklar (Studio'nun "Teklif ekranı oluştur" şablonu ilk ölçümde üçüne düştü): akış `start` adımıyla açılmazsa motor
100
+ onu sessizce bitirir, düğme hiçbir şey yapmaz; kod bağı `{{ }}` ile sarılmazsa (`isHidden: 'Entitlements.premium'`)
101
+ düz metin sayılır ve iki kök birden gizlenir; hata dalı yalnız `pending`'i karşılarsa öteki hatalar sessiz kalır.
102
+ Statik anahtar (`{{ }}` şablonu olmayan değer) katalogda değilse yazım `FLOW-045` ile **durur**, hata katalogdaki
103
+ anahtarları sayar: ürünü önce `mobile_product_upsert` ile açın (akış doğrulayıcısı kataloğu okumaz; Studio'da yazılan
104
+ bilinmeyen anahtarı mağaza yayını ön kontrolü P-23 uyarır). Şablon değer yazımda denetlenmez. Nerede ne olur: Studio önizlemesi, canlı kip, `/dev`,
105
+ `/branch` ve Zeyrek Mobile oynatıcısı gerçek satın alma yapmaz, dört sonuçlu **deneme penceresi** açar (`preview-purchase__success` ·
106
+ `__cancel` · `__pending` · `__error`, gönder `preview-purchase__send`; başarı yalnız o oturumda hakkı açar, Haklar çipi
107
+ de açar); production `/view` ve web uygulaması `unsupported`. `page_screenshot` pencereyi biyometri penceresi gibi
108
+ tanımaz — `scan-code`'daki gibi `captureUnready: true` ile sürün (satın alma penceresinde ölçülmedi). Gerçek satın alma
109
+ kabuk 0.9.0+ mağaza derlemesinde (TestFlight sandbox, Play iç test): hak sunucuda doğrulanır, telefonun söylediğine
110
+ güvenilmez; cihazın doğrulama/geri yükleme uçlarını kabuk çağırır, akıştan çağrılmaz. Satışlar `mobile_purchase_list`.
111
+
112
+ ## Kredi harcama tarifi
113
+
114
+ **Kredi harcama tarifi** (kök ADR 0027 bakiye bölümü): (1) kredi ürünü kataloğa — `mobile_product_upsert { key: 'kredi_100',
115
+ kind: 'consumable', balance: 'kredi', grantsAmount: 100, priceTry, texts }`; aynı `balance`'ı birden çok ürün paylaşır
116
+ (`kredi_100`, `kredi_500` → `Balances.kredi`), `entitlement` verilmez. Satın alma teklif ekranıyla aynıdır (`purchase`
117
+ `{ value: 'kredi_100' }`): doğrulanınca bakiye `grantsAmount` kadar artar. (2) Ekran bakiyeyi okur:
118
+ `{{ 'Kredin: ' + (Balances.kredi ?? 0) }}` — bilinmeyen bakiye `undefined`, `?? 0` yazın; `{{ }}` ile sarın. (3) Harcayan
119
+ düğmenin akışı `start` → `balance-spend { value: 'kredi', title: '10' }`; `always` işi yapar, `error` dalında
120
+ `Steps.<ad>.error.status === 'insufficient'` → "Krediniz yetmiyor" ve satın alma ekranına yönlendirme, `unavailable` →
121
+ "Bağlantı gerekli" (çevrimdışıyken harcanamaz, kuyruğa girmez). (4) Sunucu tarafı iş (rapor üretmek) harcamadan SONRA
122
+ koşar: harcamayı önce yapan akış, iş düşerse bakiyeyi iade etmez — sunucuda "düş, üret, düşerse geri ekle" gerekiyorsa
123
+ workflow `balance` adımıyla kurun (`platform_guide('workflow')` §Balance adımı). Tuzaklar: miktar `args.title`'dır
124
+ (`args.amount` sessizce düşer, `FLOW-046` block); bakiye adı katalogdaki `balance`'tır, ürün anahtarı değil; **girişsiz
125
+ uygulamada bakiye kuruluma yazılır ve tüketilen ürün mağazada geri yüklenemez** — telefon değişince kredi gider
126
+ (`MOB-036` yazımda uyarır); girişli uygulamada hesaba taşınır. Nerede ne olur: production `/view` ve web uygulaması
127
+ `unsupported` döner; gerçek harcama kabuk 0.9.0+ mağaza derlemesinde sunucuda doğrulanır. Studio önizlemesinde kredi
128
+ satın alma/harcama davranışı için frontend 11 (kabukta tüketme ve önizleme) bitene kadar bu rehberde ölçülmüş bir şey
129
+ yoktur — önizlemede `unsupported` ya da deneme değeri görürseniz hata değildir. Tam gövde:
130
+ `ngapps://examples/flow-credit-spend`.
131
+
81
132
  ## Konum ve harita
82
133
 
83
134
  `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
@@ -374,6 +374,35 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
374
374
  | `automation` | Entity satır olayına tepki | `entityId` + `operation` + `isQueue` | returnDtoId YASAK; aşağıdaki "Automation gerçekleri" |
375
375
  | `middleware` | Microservice/sayfa **çalışmadan önce** koşan guard/setter (auth, `user`/`settings` değişkenleri) | Bağlandığı uç çağrılınca (`middlewareIds`) | returnDtoId gerekmez; `set-user`/`variable`/`setcookie`/`redirect` yalnız bu tipte. **Ayrı rehberi var: `platform_guide('middleware')`** |
376
376
 
377
+ ## Balance adımı: bakiye oku, düş, ekle, hak var mı (kök ADR 0027, `StepType.Balance`)
378
+
379
+ Mobil uygulamanın tüketilen ürün bakiyesini (kredi, jeton) sunucu iş akışından yönetir. İşlem `rule.balanceOperation`
380
+ (`read` · `spend` · `grant` · `entitled`), girdi `rule.expression` nesne literalidir (parantezle ya da doğrudan `{ … }`):
381
+ `application` (mobil uygulama slug'ı) ya da `applicationId`, `userId` (yoksa oturumdaki uygulama kullanıcısı) ya da
382
+ `installationId`, `balance`, `amount` (tam sayı ya da metin), `reason` (`grant`, ≤ 200), `idempotencyKey`, `entitlement`
383
+ (`entitled`). Çıktı `read`/`spend`/`grant`'ta `{ balance, balances }`, `entitled`'da `{ entitled, expiresAt }` (birden çok
384
+ açık satırdan biri süresizse `expiresAt: null`). Hata — yetersiz bakiye, geçersiz girdi, kullanıcı yok — adımın **hata
385
+ dalına** gider; `BalanceInsufficientException` kodu `errorCode` olarak okunur. Üç workflow tipinde de serbesttir.
386
+
387
+ - **Bakiye adı katalogdadır:** statik `balance` uygulamanın tüketilen ürünlerinden birinin `balance` alanı, statik
388
+ `entitlement` bir ürünün `entitlement`'ı olmalı — olmayan ad yazımda `FLOW-046` ile durur (hata katalogdakileri sayar;
389
+ `mobile_product_list`). İfadeyle (`input.ad`) verilen ad yazımda denetlenmez. Katalog akışın uygulamasından (ya da
390
+ ifadedeki GUID `applicationId`'den) okunur; okunamazsa kural susar.
391
+ - **Tekrar güvenliği varsayılan DEĞİLDİR:** `idempotencyKey` verilmezse adım çalışma kimliği kullanılır ve o her çalışmada
392
+ yenidir — yeniden koşabilen akış (yeniden deneme, döngü, aynı olayın iki kez gelmesi) iki kez düşer/ekler. `spend`/`grant`
393
+ için olayı tanımlayan bir anahtar verin (`idempotencyKey: input.davetId`); `FLOW-046` anahtarsız `spend`/`grant`'ı
394
+ uyarır. `spend:` ve `grant:` anahtar uzayları ayrıdır.
395
+ - **Tarif — krediyi düş, işi yap, iş düşerse geri ekle:** (1) `balance` `spend` `{ balance: 'kredi', amount: 10,
396
+ idempotencyKey: input.istekId }`; `success` → (2) işi yapan adım (ör. `http`/`microfunction`); onun `fail` dalı → (3)
397
+ `balance` `grant` `{ balance: 'kredi', amount: 10, reason: 'rapor üretilemedi', idempotencyKey: 'iade-' + input.istekId }`;
398
+ `spend`'in kendi hata dalı (`insufficient`) → `response` 409 gövdesi. Önce düşmek yarışta bakiyeyi korur; iade ayrı
399
+ anahtarlı olduğu için tekrar koşarsa bir kez yazılır. Defterde `grant` satırı mağaza iadesinden (`refund`) ayrı türdür.
400
+ - **Kilit:** `spend`/`grant` aynı sahibin eşzamanlı harcamasını sıralayan danışma kilidini işlem sonuna kadar tutar —
401
+ `balance` adımını akışın sonuna yakın ve kısa tutun, ondan sonra uzun süren adım (HTTP, e-posta) koyma.
402
+ - `entitled` sunucudaki hak satırlarına bakar (açık ve süresi geçmemiş); istemci ne yollarsa yollasın cevap değişmez —
403
+ istemci tarafı `Entitlements.<hak>` yalnız ekranı gizler, sunucu denetimi bunun yerine geçmez.
404
+ - Önizleme (`workflow_preview`) işlemi geri alır: `grant`/`spend` gerçekten koşar ve kalıcı satır bırakmaz.
405
+
377
406
  ## Step tipi × workflow tipi izin matrisi
378
407
 
379
408
  | Step | workflow | automation | middleware |
@@ -384,6 +413,7 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
384
413
  | convert | ✓ | – | – |
385
414
  | revert (Fail and Rollback) | – | ✓ | – |
386
415
  | process (BPM süreci) | ✓ | ✓ | – |
416
+ | balance (uygulama bakiyesi / hak denetimi) | ✓ | ✓ | ✓ |
387
417
 
388
418
  > **`process` adımı süreci başlatır ya da ona sinyal/vekalet yazar, BEKLEMEZ (2026-09-18).**
389
419
  > Kural alanları `processSlug`, `processOperation: start | signal | delegate` (boş = `start`) ve
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "d916ee9592bccbc43433515ace6770949c374478ac53123ea894bab76bcdfd02",
3
- "generatedAt": "2026-10-28T17:19:04.043Z",
2
+ "hash": "b073fadf0690bedd0b74c8c0610e0b843cd7487f3e2c7e9e27361443c52c06be",
3
+ "generatedAt": "2026-11-01T20:55:00.329Z",
4
4
  "types": 377,
5
5
  "files": 196,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
@@ -233,7 +233,8 @@
233
233
  "revert",
234
234
  "set-user",
235
235
  "microfunction",
236
- "process"
236
+ "process",
237
+ "balance"
237
238
  ],
238
239
  "type": "string",
239
240
  "format": ""
@@ -349,6 +350,10 @@
349
350
  "type": "string",
350
351
  "nullable": true
351
352
  },
353
+ "balanceOperation": {
354
+ "type": "string",
355
+ "nullable": true
356
+ },
352
357
  "queryId": {
353
358
  "type": "string",
354
359
  "format": "uuid",
@@ -254,6 +254,10 @@
254
254
  "push-notification",
255
255
  "mobile-build",
256
256
  "mobile-runner",
257
+ "mobile-store-product",
258
+ "mobile-store-import",
259
+ "app-purchase-import",
260
+ "mobile-store-diff",
257
261
  "organization-schema",
258
262
  "organization-unit",
259
263
  "organization-position",
@@ -171,7 +171,8 @@
171
171
  "revert",
172
172
  "set-user",
173
173
  "microfunction",
174
- "process"
174
+ "process",
175
+ "balance"
175
176
  ],
176
177
  "type": "string",
177
178
  "format": ""
@@ -287,6 +288,10 @@
287
288
  "type": "string",
288
289
  "nullable": true
289
290
  },
291
+ "balanceOperation": {
292
+ "type": "string",
293
+ "nullable": true
294
+ },
290
295
  "queryId": {
291
296
  "type": "string",
292
297
  "format": "uuid",
@@ -76,7 +76,8 @@
76
76
  "revert",
77
77
  "set-user",
78
78
  "microfunction",
79
- "process"
79
+ "process",
80
+ "balance"
80
81
  ],
81
82
  "type": "string",
82
83
  "format": ""
@@ -192,6 +193,10 @@
192
193
  "type": "string",
193
194
  "nullable": true
194
195
  },
196
+ "balanceOperation": {
197
+ "type": "string",
198
+ "nullable": true
199
+ },
195
200
  "queryId": {
196
201
  "type": "string",
197
202
  "format": "uuid",
@@ -233,7 +233,8 @@
233
233
  "revert",
234
234
  "set-user",
235
235
  "microfunction",
236
- "process"
236
+ "process",
237
+ "balance"
237
238
  ],
238
239
  "type": "string",
239
240
  "format": ""
@@ -349,6 +350,10 @@
349
350
  "type": "string",
350
351
  "nullable": true
351
352
  },
353
+ "balanceOperation": {
354
+ "type": "string",
355
+ "nullable": true
356
+ },
352
357
  "queryId": {
353
358
  "type": "string",
354
359
  "format": "uuid",
package/dist/server.js CHANGED
@@ -54,6 +54,8 @@ import { registerBpmReportingTools } from "./tools/bpmReporting.js";
54
54
  import { registerModuleTools } from "./tools/modules.js";
55
55
  import { registerMobileTools } from "./tools/mobile.js";
56
56
  import { registerMobileStoreTools } from "./tools/mobileStore.js";
57
+ import { registerMobilePurchaseTools } from "./tools/mobilePurchases.js";
58
+ import { registerMobileStoreImportTools } from "./tools/mobileStoreImport.js";
57
59
  import { registerAssistantTools } from "./tools/assistant.js";
58
60
  const moduleRegistrations = {
59
61
  platform: registerPlatformTools,
@@ -111,6 +113,8 @@ const moduleRegistrations = {
111
113
  mobile: (server, platform) => {
112
114
  registerMobileTools(server, platform);
113
115
  registerMobileStoreTools(server, platform);
116
+ registerMobilePurchaseTools(server, platform);
117
+ registerMobileStoreImportTools(server, platform);
114
118
  registerMobilePublishPrompt(server);
115
119
  },
116
120
  assistant: registerAssistantTools,
@@ -17,6 +17,8 @@ import { randomUUID } from 'node:crypto';
17
17
  import { flowProfileWarnings } from './profileWrite.js';
18
18
  import { enforceTextHooks } from './textHookGuard.js';
19
19
  import { readMobileOffline } from '../knowledge/platform/mobileOffline.js';
20
+ import { restorePathWarnings, subscriptionDisclosureWarnings } from '../knowledge/platform/mobileRules.js';
21
+ import { balanceCatalogFindings, balanceWithoutSignInWarnings, purchaseCatalogFindings } from '../knowledge/mobileStore.js';
20
22
  import { resolveProfile } from '../knowledge/platform/resolve.js';
21
23
  import { jsonArray } from '../platform/jsonArray.js';
22
24
  import { z } from 'zod';
@@ -399,6 +401,60 @@ async function uncachedReadWarnings(platform, applicationId, steps, errorSteps,
399
401
  }
400
402
  return warnings;
401
403
  }
404
+ /**
405
+ * FLOW-046 katalog ayağı ve MOB-036 (g-02): yazılan akışta `balance-spend` varsa aynı katalog okumasından beslenir. Statik bakiye
406
+ * adı uygulamanın tüketilen ürünlerinde yoksa `block` (FLOW-045 ile aynı kalıp); tüketilen ürün var ama giriş ucu yoksa MOB-036
407
+ * uyarısı için uygulamanın microservice listesi okunur (okunamazsa susar).
408
+ *
409
+ * FLOW-045 katalog ayağı, MOB-034 ve MOB-042 (kök ADR 0027, plans/mobil-uygulama-ici-satin-alma g-01). Üçü de tanımda
410
+ * olmayan bilgiyi ister — uygulamanın ürün kataloğu (Core `mobile-store/{appId}/products`), ekranın öteki akışları ve
411
+ * sayfanın ağacı — ve yalnız yazılan akışta `purchase` adımı varken okunur. Katalogda olmayan statik anahtar `block`
412
+ * bulgusudur (`findings`; çağıran yazımdan ÖNCE `applyPolicyMode` + `enforcePolicy` ile uygular — CD-005 kalıbı), öteki
413
+ * ikisi uyarıdır. Web uygulamasında adım her zaman `unsupported` döner (barındırıcı satın alma taşımaz): katalog
414
+ * okunmaz, yalnız uyarı. Okunamayan katalog ya da akış listesi bulgu üretmez — bilmediğimizi ret diye söylemeyiz.
415
+ */
416
+ async function purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, steps, errorSteps) {
417
+ const written = [...(Array.isArray(steps) ? steps : []), ...(Array.isArray(errorSteps) ? errorSteps : [])].filter(isRecord);
418
+ const purchases = written.filter(step => String(step.type ?? '') === 'purchase');
419
+ const spends = written.filter(step => String(step.type ?? '') === 'balance-spend');
420
+ if (purchases.length === 0 && spends.length === 0)
421
+ return { findings: [], warnings: [] };
422
+ const resolved = await resolveProfile(platform, applicationId);
423
+ if (resolved.known && resolved.profile.platform !== 'mobile') {
424
+ return {
425
+ findings: [],
426
+ warnings: [
427
+ ...(purchases.length > 0 ? [`FLOW-045: '${String(purchases[0]?.stepId ?? purchases[0]?.name ?? '?')}' purchase adımı bu web uygulamasında her zaman 'unsupported' döner — uygulama içi satın alma yalnız mobil uygulamanın mağaza derlemesindedir (önizlemede deneme penceresi).`] : []),
428
+ ...(spends.length > 0 ? [`FLOW-046: '${String(spends[0]?.stepId ?? spends[0]?.name ?? '?')}' balance-spend adımı bu web uygulamasında her zaman 'unsupported' döner — bakiye harcama yalnız mobil uygulamanın mağaza derlemesindedir (önizlemede deneme penceresi).`] : [])
429
+ ]
430
+ };
431
+ }
432
+ const products = await platform.org('GET', `/mobile-store/${applicationId}/products`)
433
+ .then(payload => {
434
+ const data = isRecord(payload) && 'data' in payload ? payload.data : payload;
435
+ return Array.isArray(data) ? data.filter(isRecord) : undefined;
436
+ }, () => undefined);
437
+ const otherFlows = purchases.length > 0
438
+ ? await listHostFlows(platform, host, project, environment, applicationId, hostId, async () => hostRecord)
439
+ .then(flows => flows.filter(flow => String(flow.id ?? '').toLowerCase() !== flowId.toLowerCase()), () => undefined)
440
+ : undefined;
441
+ const microservices = spends.length > 0 && products
442
+ ? await platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query: { pageLength: 200 } })
443
+ .then(payload => {
444
+ const data = isRecord(payload) && 'data' in payload ? payload.data : payload;
445
+ const items = isRecord(data) && Array.isArray(data.items) ? data.items : data;
446
+ return Array.isArray(items) ? items.filter(isRecord) : undefined;
447
+ }, () => undefined)
448
+ : undefined;
449
+ return {
450
+ findings: products ? [...purchaseCatalogFindings(purchases, products), ...balanceCatalogFindings(spends, products)] : [],
451
+ warnings: [
452
+ ...(otherFlows ? restorePathWarnings(written, otherFlows, products) : []),
453
+ ...(products && purchases.length > 0 && host === 'page' ? subscriptionDisclosureWarnings(written, products, hostNodes(hostRecord), otherFlows ?? []) : []),
454
+ ...(products && spends.length > 0 ? balanceWithoutSignInWarnings(products, microservices) : [])
455
+ ]
456
+ };
457
+ }
402
458
  /**
403
459
  * FLOW-038 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
404
460
  * kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
@@ -534,6 +590,8 @@ export function registerFlowTools(server, platform) {
534
590
  where: `akış '${name}'`
535
591
  }, policyOverride);
536
592
  const readService = serviceDefinitionReader(platform, project, environment, applicationId);
593
+ const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, writtenFlowId, steps, errorSteps ?? []);
594
+ const purchaseFindings = enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
537
595
  const parameterWarnings = [
538
596
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
539
597
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
@@ -541,7 +599,8 @@ export function registerFlowTools(server, platform) {
541
599
  ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
542
600
  ...microserviceAlwaysDataWarnings(steps, errorSteps ?? []),
543
601
  ...await queuedBranchWarnings(platform, applicationId, steps, errorSteps ?? [], readService),
544
- ...await uncachedReadWarnings(platform, applicationId, steps, errorSteps ?? [], readService)
602
+ ...await uncachedReadWarnings(platform, applicationId, steps, errorSteps ?? [], readService),
603
+ ...purchase.warnings
545
604
  ];
546
605
  // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
547
606
  // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
@@ -569,7 +628,7 @@ export function registerFlowTools(server, platform) {
569
628
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
570
629
  ...(platformWarnings.length > 0 ? { platformWarnings } : {}),
571
630
  hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
572
- }, normalized), drops.renamed), drops), [...findings, ...textFindings]);
631
+ }, normalized), drops.renamed), drops), [...findings, ...textFindings, ...purchaseFindings]);
573
632
  }));
574
633
  server.registerTool('flow_update', {
575
634
  title: 'Akış güncelle',
@@ -624,6 +683,9 @@ export function registerFlowTools(server, platform) {
624
683
  }, policyOverride)
625
684
  : [];
626
685
  const readService = serviceDefinitionReader(platform, project, environment, applicationId);
686
+ const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, flow.steps, flow.errorSteps);
687
+ // Kayıtlı gövde suçlanmaz: yalnız ad/slug değişen yazımda katalog bulgusu uygulanmaz (FLOW-* kuralları gibi).
688
+ const purchaseFindings = enforcePolicy(config.policyMode === 'off' || !(steps || errorSteps) ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
627
689
  const parameterWarnings = [
628
690
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
629
691
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
@@ -631,7 +693,8 @@ export function registerFlowTools(server, platform) {
631
693
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
632
694
  ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
633
695
  ...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
634
- ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
696
+ ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
697
+ ...purchase.warnings
635
698
  ];
636
699
  await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
637
700
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
@@ -644,7 +707,7 @@ export function registerFlowTools(server, platform) {
644
707
  });
645
708
  const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
646
709
  return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
647
- ...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), [...findings, ...textFindings]);
710
+ ...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), [...findings, ...textFindings, ...purchaseFindings]);
648
711
  }));
649
712
  server.registerTool('flow_patch', {
650
713
  title: "Akışa patch uygula",
@@ -690,6 +753,8 @@ export function registerFlowTools(server, platform) {
690
753
  }, policyOverride)
691
754
  ];
692
755
  const readService = serviceDefinitionReader(platform, project, environment, applicationId);
756
+ const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, flow.steps, flow.errorSteps);
757
+ const purchaseFindings = enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
693
758
  const parameterWarnings = [
694
759
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
695
760
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
@@ -697,7 +762,8 @@ export function registerFlowTools(server, platform) {
697
762
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
698
763
  ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
699
764
  ...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
700
- ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
765
+ ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
766
+ ...purchase.warnings
701
767
  ];
702
768
  // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
703
769
  const netOps = fastJsonPatch.compare(current, flow);
@@ -717,7 +783,7 @@ export function registerFlowTools(server, platform) {
717
783
  flow: flowSummary(written.data),
718
784
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
719
785
  ...(platformWarnings.length > 0 ? { platformWarnings } : {})
720
- }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), findings);
786
+ }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), [...findings, ...purchaseFindings]);
721
787
  }));
722
788
  server.registerTool('flow_delete', {
723
789
  title: 'Akış sil',