@appaflytech/zeyrek-ai-mcp 0.0.62 → 0.0.64

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.
Files changed (43) hide show
  1. package/.env.example +6 -0
  2. package/dist/config.js +2 -0
  3. package/dist/knowledge/backendConstraints.js +2 -2
  4. package/dist/knowledge/componentCatalog.js +1 -1
  5. package/dist/knowledge/mobileStore.js +24 -4
  6. package/dist/knowledge/normalize.js +10 -0
  7. package/dist/knowledge/platform/mobileLayoutLint.js +8 -0
  8. package/dist/knowledge/platform/mobileRules.js +6 -0
  9. package/dist/knowledge/rules.js +89 -0
  10. package/dist/observability/gapSink.js +43 -0
  11. package/dist/platform/argumentAliases.js +6 -0
  12. package/dist/platform/capture/actions.js +71 -2
  13. package/dist/platform/capture/capture.js +5 -2
  14. package/dist/platform/responses.js +5 -2
  15. package/dist/platform/writeDiff.js +52 -1
  16. package/dist/resources/examples/middleware-password-reset.json +1281 -0
  17. package/dist/resources/examples/mobile-reorder-persist.json +33 -7
  18. package/dist/resources/examples/workflow-email-step.json +67 -0
  19. package/dist/resources/guides/deployment.md +13 -1
  20. package/dist/resources/guides/entity.md +7 -0
  21. package/dist/resources/guides/expressions.md +5 -0
  22. package/dist/resources/guides/getting-started.md +15 -30
  23. package/dist/resources/guides/microservice.md +30 -2
  24. package/dist/resources/guides/middleware.md +83 -3
  25. package/dist/resources/guides/mobile-layout.md +35 -3
  26. package/dist/resources/guides/mobile-publish.md +33 -1
  27. package/dist/resources/guides/mobile-steps.md +12 -2
  28. package/dist/resources/guides/mobile.md +11 -8
  29. package/dist/resources/guides/page.md +28 -3
  30. package/dist/resources/guides/translation.md +6 -0
  31. package/dist/resources/guides/workflow.md +30 -1
  32. package/dist/resources/schema-fingerprint.json +2 -2
  33. package/dist/tools/applications.js +7 -4
  34. package/dist/tools/common.js +8 -0
  35. package/dist/tools/designSchemaPatch.js +4 -1
  36. package/dist/tools/flows.js +14 -4
  37. package/dist/tools/layouts.js +3 -1
  38. package/dist/tools/microservices.js +8 -2
  39. package/dist/tools/mobileStore.js +13 -2
  40. package/dist/tools/pages.js +27 -12
  41. package/dist/tools/platformInfo.js +15 -6
  42. package/dist/tools/workflows.js +4 -2
  43. package/package.json +1 -1
@@ -3,16 +3,16 @@
3
3
  "synthetic": true,
4
4
  "note": "Sırası kalıcı kaydedilen mobil liste: repeater tutamaçtan sürüklenir, reorder akışı event.data.patches'ı bulk-update ucuna TEK istekte kök dizi olarak yazar, ardından listeyi sıra alanına göre yeniden okur. mobiltest › siralama-kalici'de (lokal, 2026-10-27) uçta ölçülen sayfanın kimliksiz hâli: sürükleme → tek PUT 200, yeniden okunan satırlar yeni sırada. Sayfa mizanpaj sözleşmesine ve MOB-035'e uyar, akışlar mobil profilden ve kendi lint kurallarından bulgusuz geçer (test/mobile-rules.test.ts, test/mobile-layout-lint.test.ts).",
5
5
  "pairsWith": "mobile-offline-list (aynı listenin okuma deseni), mobile-list-detail-form (satıra dokunma), mobile-app-shell-stack ya da mobile-app-shell-tab (kabuk)",
6
- "sourceRef": "platform_guide('page') §Kural 1g-5; platform_guide('microservice') §Toplu yazma; MOB-035; zeyrek-frontend docs/adr/0029-akis-adiminda-kok-dizi-govde.md"
6
+ "sourceRef": "platform_guide('page') §Kural 1g-5; platform_guide('microservice') §Toplu yazmada sahiplik (MS-027); MOB-035; zeyrek-frontend docs/adr/0029-akis-adiminda-kok-dizi-govde.md"
7
7
  },
8
8
  "_rules": {
9
9
  "list": "repeater'a reorderable: true + orderField: '<sayı alanı>' yazılır; tutamaç her satırın sonunda çizilir. Yalnız dikey ve tek sütunlu listede çalışır — direction: 'horizontal' ya da columnCount > 1 MOB-035 uyarısıdır ve tutamaç çizilmez. Satırlar orderField'a göre çizilir; okuma ucunun sıralı dönmesi yine de yenilemede aynı sırayı garanti eder.",
10
10
  "payload": "Bırakınca reorder yayılır: event.data = { fromIndex, toIndex, row, patches: [{ id, value }] }. patches yalnız sırası değişen satırlardır; id satırın _id'si (yoksa id), value orderField'a yazılacak 1 tabanlı yeni adım. Yerinde bırakılan sürükleme olay yaymaz; orderField yoksa ya da bir satırda anahtar yoksa patches BOŞ dizidir.",
11
- "endpoint": "Yazma ucu tablo kaynaklı operation: 'bulk-update'; fields = birincil anahtar + sıra alanı, başka alan yok (tanımdaki alan gövdede yoksa NULL yazılır). Gövdede birincil anahtar zorunludur, WHERE'e taşınır. Tek istekte en fazla 1000 satır, parti hep-ya-hiç.",
11
+ "endpoint": "Yazma ucu tablo kaynaklı operation: 'bulk-update'; fields = birincil anahtar + sıra alanı, başka alan yok (tanımdaki alan gövdede yoksa NULL yazılır). Gövdede birincil anahtar zorunludur, WHERE'e taşınır. Tek istekte en fazla 1000 satır, parti hep-ya-hiç. Uç oturum ister (middlewareIds: auth middleware) ve filters'ta sahip alanını kapsar: { field: <sahip alanı>, operator: 'eq', source: 'expression', value: 'user.id' }. Platform bu kapsamı her satırın UPDATE'ine koyar; başkasının kimliği gelirse bütün parti 404 ile reddedilir, hiçbir satır yazılmaz (MS-027, zeyrek-backend ADR 0074). Kapsam alanını fields'a koymayın: uç onu yazarsa kayıt reddedilir.",
12
12
  "bodyKeys": "Gövde anahtarları alan slug'larıdır; fields[].name ile yeniden adlandırma yoktur. { id, value } yaması uca olduğu gibi gitmez: map ile { id, <orderField>: value }'ya eşlenir.",
13
13
  "rootArray": "Satır listesi ucun gövde parametrelerinden YALNIZ BİRİNE (birincil anahtarınkine) code ile verilir, öbürleri hiç yazılmaz. Servis şeması isParameterArray dediği için değeri olan tek gövde parametresinin dizisi gövdenin KENDİSİ olur — parametre adı anlamını yitirir. İkinci bir gövde parametresine değer verilirse gövde nesneye döner ve platform tek satır okur: 422 'Please provide the primary key value'. ADR 0029'dan eski platformda bu akış da 422 ile düşer.",
14
14
  "onError": "Servis hata dönerse liste kendiliğinden eski sıraya DÖNMEZ: yeniden okuma sürüklemeden önceki satırların aynısını getirir, liste aynı satırlarda tuttuğu sırayı korur ve ekran kaydedilmemiş sırada kalır. Hatayı Steps.<yazma adımı>.error ile kullanıcıya gösterin; eski sıraya döndürmenin doğrulanmış bir yolu yoktur.",
15
- "verify": "page_screenshot({ applicationSlug: '<uygulama>', pageSlug: 'siralama', actions: [{ action: 'swipe', target: 'lst_gorev__row-2__handle', to: 'lst_gorev__row-0' }] }) üçüncü satırı başa taşır; txt_sonuc 'kaydedildi 3 satır' yazar ve row_list (order sira) aynı sırayı verir. Tutamaç kimliği yalnız çalışma anında basılır. Her denemeden sonra satırları bulk-update ile ilk sıralarına geri yazın."
15
+ "verify": "page_screenshot({ applicationSlug: '<uygulama>', pageSlug: 'siralama', actions: [{ action: 'swipe', target: 'lst_gorev__row-2__handle', to: 'lst_gorev__row-0' }] }) üçüncü satırı başa taşır; txt_sonuc 'kaydedildi 3 satır' yazar ve row_list (order sira) aynı sırayı verir. Tutamaç kimliği yalnız çalışma anında basılır. Her denemeden sonra satırları bulk-update ile ilk sıralarına geri yazın. Sahiplik: aynı ucu ikinci bir kullanıcının oturumuyla, gövdeye birinci kullanıcının bir satır kimliği koyarak microservice_execute ile çağırın — yanıt 404, row_list'te hiçbir satırın sırası değişmez."
16
16
  },
17
17
  "microservices": [
18
18
  {
@@ -33,7 +33,17 @@
33
33
  "alias": "gorev",
34
34
  "isSingle": false,
35
35
  "joins": [],
36
- "filters": [],
36
+ "filters": [
37
+ {
38
+ "filterType": "single",
39
+ "conditionType": "and",
40
+ "alias": "gorev",
41
+ "fieldSlug": "sahip",
42
+ "operator": "eq",
43
+ "source": "expression",
44
+ "value": "user.id"
45
+ }
46
+ ],
37
47
  "selectFields": [
38
48
  {
39
49
  "alias": "gorev",
@@ -58,7 +68,10 @@
58
68
  "offset": "offset",
59
69
  "source": "query-string"
60
70
  }
61
- }
71
+ },
72
+ "middlewareIds": [
73
+ "<auth middleware id>"
74
+ ]
62
75
  }
63
76
  },
64
77
  {
@@ -79,7 +92,17 @@
79
92
  "alias": "gorev",
80
93
  "isSingle": true,
81
94
  "joins": [],
82
- "filters": [],
95
+ "filters": [
96
+ {
97
+ "filterType": "single",
98
+ "conditionType": "and",
99
+ "alias": "gorev",
100
+ "fieldSlug": "sahip",
101
+ "operator": "eq",
102
+ "source": "expression",
103
+ "value": "user.id"
104
+ }
105
+ ],
83
106
  "selectFields": [],
84
107
  "fields": [
85
108
  {
@@ -104,7 +127,10 @@
104
127
  "offset": "offset",
105
128
  "source": "query-string"
106
129
  }
107
- }
130
+ },
131
+ "middlewareIds": [
132
+ "<auth middleware id>"
133
+ ]
108
134
  }
109
135
  }
110
136
  ],
@@ -0,0 +1,67 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": true,
4
+ "note": "email adımı: parametreden gelen tek alıcıya HTML ileti gönderir; başarıda 200, alıcı yanlışsa ya da SMTP düşerse fail dalından 400 döner. Alıcı YALNIZ rule.expression ile verilir ve { to, cc, bcc } adres dizgisi listeleri döndürür. Backend sözleşmesi koddan okundu ve testle sabitlendi (zeyrek-backend EmailStepTests, incident #197, 2026-11-04); aynı adım şekli lokal yığında ölçüldü (2026-11-04, workflow_preview → mailpit'e tek ileti, doğru alıcı; to: [] → fail dalı EmailRecipientsMissingException).",
5
+ "sourceRef": "platform_guide('workflow') §Email adımı; WF-037; zeyrek-backend EmailStep.cs EvaluateAsync, StepSchemaGenerator.cs (email)"
6
+ },
7
+ "_rules": {
8
+ "alici": "Alıcı rule.expression: \"({ to: [input.email] })\". cc ve bcc aynı biçimde isteğe bağlıdır. Öğe adres DİZGİSİDİR; { type, value } nesnesi EmailRecipientInvalidException ile fail dalına düşer.",
9
+ "okunmayanAnahtarlar": "rule.send / send.toMail / to / toMail / sendTo / recipients kural düzeyinde OKUNMAZ, platform bağlarken atar; gateway WF-037 ile kaydı durdurur. workflow_step_schema'daki expression.to/cc/bcc ifadenin döndürdüğü şekildir.",
10
+ "govde": "rule.body HTML olarak gider; şablon dizgisiyle değer gömülür. rule.subject konu, rule.displayName gönderen ADI (gönderen adresi platform ayarıdır).",
11
+ "hata": "Alıcı yoksa EmailRecipientsMissingException, öğe dizgi değilse EmailRecipientInvalidException, SMTP hatası da aynı fail dalına düşer; <adım>.error.code hata anahtarını verir.",
12
+ "verify": "workflow_preview({ input: { email: 'deneme@zeyrek.local' } }) → 200 { gonderildi: true }; curl -s localhost:8025/api/v1/messages son iletide To=deneme@zeyrek.local, Subject='Doğrulama kodu'. Önizleme gerçekten gönderir."
13
+ },
14
+ "definition": {
15
+ "name": "Doğrulama Kodu Gönder",
16
+ "slug": "dogrulama-kodu-gonder",
17
+ "description": "Verilen adrese tek kullanımlık doğrulama kodu e-postası gönderir.",
18
+ "type": "workflow",
19
+ "parameterDtoId": "<girdi DTO id: { email: string }>",
20
+ "parameterBehavior": "object",
21
+ "returnDtoId": "<çıktı DTO id: { gonderildi: boolean, hata?: string }>",
22
+ "returnBehavior": "object",
23
+ "steps": [
24
+ {
25
+ "id": "<GUID-1>",
26
+ "name": "Start",
27
+ "type": "start",
28
+ "description": "Akışı başlatır.",
29
+ "branches": [{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" }]
30
+ },
31
+ {
32
+ "id": "<GUID-3>",
33
+ "name": "kodGonder",
34
+ "type": "email",
35
+ "description": "Kodu girdideki adrese HTML ileti olarak gönderir.",
36
+ "rule": {
37
+ "stepType": "email",
38
+ "expression": "({ to: [input.email] })",
39
+ "subject": "'Doğrulama kodu'",
40
+ "body": "`<p>Doğrulama kodunuz: <b>482913</b></p><p>Kod 10 dakika geçerlidir.</p>`",
41
+ "displayName": "'Defter'",
42
+ "attachment": "[]"
43
+ },
44
+ "branches": [
45
+ { "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-5>" },
46
+ { "id": "<GUID-6>", "name": "fail", "type": "fail", "nextStepId": "<GUID-7>" }
47
+ ]
48
+ },
49
+ {
50
+ "id": "<GUID-5>",
51
+ "name": "Gonderildi",
52
+ "type": "response",
53
+ "description": "Gönderimin başarılı olduğunu döner.",
54
+ "rule": { "stepType": "response", "bodyExpression": "({ gonderildi: true })", "httpStatusCode": 200 },
55
+ "branches": []
56
+ },
57
+ {
58
+ "id": "<GUID-7>",
59
+ "name": "Gonderilemedi",
60
+ "type": "response",
61
+ "description": "Gönderim hatasının anahtarını döner.",
62
+ "rule": { "stepType": "response", "bodyExpression": "({ gonderildi: false, hata: kodGonder.error.code })", "httpStatusCode": 400 },
63
+ "branches": []
64
+ }
65
+ ]
66
+ }
67
+ }
@@ -20,7 +20,7 @@ kazasıdır, bu yüzden her tablo ve her dosya ayrıca seçilir.
20
20
  | Uygulama asistanı | evet | ayar, rehber, beceri, senaryo ve **yayındaki paket** (beceri seti, rehber, menü, sağlama toplamı); hedefte asistan development'ta en son yayınlanan hâliyle çalışır, production'da ayrıca yayın yoktur (ASI-018). Kaynakta hiç yayın yoksa hedefin paketi korunur |
21
21
  | Tablo satırları | **yalnız tabloda kayıt taşıma açıksa** | §Tablo satırları |
22
22
  | Dosyalar | **yalnız id referansıyla** | §Dosyalar |
23
- | Kullanıcı, rol ataması, süreç örneği, görev, defter | hayır | ortama özgüdür |
23
+ | Kullanıcı, rol ataması, süreç örneği, görev, defter | hayır | ortama özgüdür; test kullanıcısı için §Test kullanıcısını production'a taşımak |
24
24
 
25
25
  Kaynak yalnız `development`'tır (DEP-003); deploy başlamadan production verisi ön denetimden geçer
26
26
  (DEP-004). İkisinin ayrıntısı `deployment_start` açıklamasında.
@@ -83,6 +83,18 @@ Satırlar **tablo başına** açılan kayıt taşıma ayarıyla gider. Ayar enti
83
83
  Kayıt taşıma deploy'un kuralıdır. Dal açarken `includeRecords: true` bu ayara bakmadan bütün veri
84
84
  tablolarını kopyalar; merge satır taşımaz (`platform_guide('merge')` §Dalda çalışmak).
85
85
 
86
+ ### Test kullanıcısını production'a taşımak
87
+
88
+ Kullanıcı ortama özgüdür ve varsayılan olarak taşınmaz. Belirli test kullanıcılarını bir kez taşımak için:
89
+
90
+ 1. development'ta `entity_patch users`: `recordTransferMode: 'specific'`, `transferRowIds: ['<kullanıcı id>', …]`,
91
+ `recordTransferSkipExisting: true` (production'da aynı kullanıcı varsa ezilmez).
92
+ 2. Deploy'u insan başlatır (production'a giden tek yol). Satır bütün sütunlarıyla gider, **parola özeti dahil**:
93
+ kullanıcı production'da development'taki parolasıyla girer. `'specific'` silmez, `users` aynalanmaz.
94
+ 3. Deploy bitince `recordTransferMode`'u `null`'a geri alın; açık kalırsa her deploy aynı satırları yeniden gönderir.
95
+ 4. Rol bağını production'da okuyarak doğrulayın (`row_list user_roles`). Eksikse rolü insan Studio'dan atar;
96
+ production'a doğrudan yazılmaz.
97
+
86
98
  ## Dosyalar
87
99
 
88
100
  `files` tablosu ortama aittir ve **toptan taşınmaz**. Bir dosya production'a yalnız iki yoldan biriyle,
@@ -405,6 +405,13 @@ sözleşmeden eski bir sürümdedir; gateway o zaman companion'a kendisi yazar v
405
405
  ile kaç dil satırı yazdığını söyler — `localizationsFailed` ise ana satır yazılmış, dil satırları
406
406
  yazılmamıştır. Aşağıdaki elle tarif yalnız o eski yol ve `rows` ile toplu yazma için gerekir.
407
407
 
408
+ **Workflow tablo adımı ve tablo kaynaklı uç** çok dilli alana yazabilir: adım şemasının sunduğu eşlik tablosu alanı
409
+ (`{slug}_localization`'daki alan) seçilir, platform değeri isteğin dilinde eşlik satırına yazar. Toplu adımda
410
+ (`bulk-create` / `bulk-update`) çok dilli değer yalnız adımın `fields` listesinde yapılandırılırsa yazılır; ifadenin
411
+ satırlarındaki çok dilli anahtar yok sayılır (bir `get` adımından okunan değer varsayılan dilin metnidir, aynen
412
+ yazılsaydı isteğin diline yanlış çeviri olurdu). 2026-11-04'ten eski backend bu yazımda `NullReferenceException`
413
+ (500) verir; o sürümde değeri ayrı bir adımla eşlik tablosuna yazın (yukarıdaki elle tarif).
414
+
408
415
  **Kullanım (tamamı mevcut genel araçlarla, yeni tool GEREKMEZ):**
409
416
 
410
417
  ```
@@ -49,6 +49,11 @@ tetikleyen satır `row` / `previousRow` / `user`.
49
49
  > sıralanabilir hem de datetime filtrelerine doğrudan verilebilir. Gün aritmetiği gerekiyorsa
50
50
  > `UtcNow()` üzerinde `getTime()` ile yapın.
51
51
  >
52
+ > **Milisaniye:** `UtcNow()` ifade içinde milisaniyeyi taşır ve tablo adımı onu milisaniyesiyle yazar (2026-11-04'ten
53
+ > eski backend saniyeye kırpar). Değeri bir değişkene yazıp SONRAKİ ifadede okursanız saniyeye düşer
54
+ > (`yyyy-MM-ddTHH:mm:ss`). Aynı saniyedeki kayıtları sıralamak için zamanı tablo adımına doğrudan verin ya da
55
+ > `getTime()` sayısını saklayın.
56
+ >
52
57
  > **Tuzak:** çözümlenemeyen bir datetime filtre değeri 400 vermez — platform o filtreyi
53
58
  > **sessizce düşürür** ve uç TÜM satırları döndürür. Bir tarih filtresi yazdıktan sonra
54
59
  > `microservice_debug` ile ifadenin neye çözüldüğüne bakın (`source:'filter'` satırları).
@@ -73,42 +73,27 @@ Bağımlılıklar bu sırayı zorunlu kılar; atlama:
73
73
 
74
74
  Onay zinciri, görev kutusu, SLA gibi insanı bekleyen işler workflow'a sıkıştırılmaz: entity ve (gerekiyorsa) servis workflow'ları hazır olduktan sonra süreç tanımı yazılır — `platform_guide('bpm')`.
75
75
 
76
- ## İlk kullanıcıyı kim açar? (tavuk-yumurta kilidi)
76
+ ## İlk kullanıcıyı kim açar?
77
77
 
78
- Kimlik kurulmuş yeni bir projede **giriş yapabilecek hiç kimse yoktur** ve zincir kendi kendini
79
- kilitler:
78
+ Kimlik kurulmuş yeni bir projede `users` tablosu **boş** gelir. Studio operatörü (`admin@…`) organizasyon
79
+ kullanıcısıdır, uygulamanın kullanıcısı DEĞİLDİR; kimse onu otomatik kopyalamaz. Middleware bağlanınca her istek
80
+ 401 döner (09-02 ve 09-04 koşularında ikisinde de yaşandı).
80
81
 
81
- 1. `users.password` alanı **hash'lidir** (`Hasher.Hash` / `crypto.SHA256.encode`, PBKDF2).
82
- `row_create` parolayı **hash'lemez** — düz metin yazar ve o kullanıcı asla giriş yapamaz
83
- (ölçüldü). Yani kullanıcı ancak `crypto.SHA256.encode(...)` çağıran bir **workflow** üzerinden
84
- açılabilir.
85
- 2. O workflow'un ucu, kimlik kurulduğu için `middlewareIds` ile auth middleware'ine bağlıdır.
86
- 3. Auth middleware'i token'ı **çerezten** okur; ortada henüz kullanıcı yoktur, dolayısıyla token
87
- da yoktur.
88
- 4. **Yeni projede `users` tablosu BOŞ gelir.** Studio operatörü (`admin@…`) organizasyon
89
- kullanıcısıdır, uygulamanın kullanıcısı DEĞİLDİR; kimse onu otomatik kopyalamaz.
90
-
91
- **Bu bir platform boşluğudur, kısa yolu yoktur.** İlk kullanıcıyı açmanın desteklenen tek yolu
92
- aşağıdaki bootstrap desenidir: geçici bir uç aç, kullanıcıyı yaz, ucu hemen sil. Bakım/migration
93
- uçlarını (`/organizations/sync-users` gibi `Sync*` uçları) **kullanma** — onlar ürün yüzeyinin
94
- parçası değildir, özellik geliştirirken veri düzeltmek için yazılmışlardır ve kaldırılmaktadırlar.
95
- Gateway de onları araç olarak yayınlamaz.
96
-
97
- Bu tur, kimlik kuran her yeni projede ödenmek zorunda: iki koşuda da (09-02 ve 09-04) `users`
98
- boş bulundu ve ikisi de bu deseni elle kurdu.
99
-
100
- **Bootstrap deseni** (crm koşumunda uygulandı):
82
+ İlk yöneticiyi **`row_create` ile `users` tablosuna** yazın. Platform `password` alanını yazarken özetler
83
+ (`Hasher.Hash`, PBKDF2) ve zaten özetli değere dokunmaz. Tek satır da toplu yazım (`rows`) da böyledir. Parolayı
84
+ düz gönderin, kendiniz özetlemeyin.
101
85
 
102
86
  ```
103
- 1. Kullanıcı yazan workflow'a bağlı, middlewareIds: [] olan GEÇİCİ bir uç aç
104
- (ör. slug: 'bootstrap-user') — parolayı workflow içinde crypto.SHA256.encode ile hash'le.
105
- 2. microservice_execute ile ilk yöneticiyi yaz (rol bağını da kur: user_roles satırı).
106
- 3. Ucu HEMEN sil (microservice_delete). Kalırsa herkese açık kullanıcı yaratma ucu bırakmış olursun.
107
- 4. Giriş ucundan token al, sonraki her şeyi normal korumalı yoldan yap.
87
+ 1. entity_get users → alan slug'larını okuyun.
88
+ 2. row_create users { …kimlik alanları…, password: '<düz parola>' } — özeti platform alır.
89
+ 3. Rol bağını kurun (user_roles satırı).
90
+ 4. Giriş ucundan token alın; sonraki her şeyi normal korumalı yoldan yapın.
108
91
  ```
109
92
 
110
- Silmeyi unutmamak için ucu açtığın anda "sil" adımını görev listene yaz; `microservice_list` ile
111
- proje kapanışında `bootstrap` içeren slug kalmadığını doğrula.
93
+ > **Eski not (2026-09):** o koşularda `row_create` parolayı düz yazıyordu ve kullanıcı geçici, korumasız bir
94
+ > workflow ucuyla açılıyordu (bootstrap deseni). 2026-11-04 ölçümünde `row_create` özetledi ve açılan kullanıcı
95
+ > girdi. Geçici uç açmayın: unutulan uç herkese açık bir kullanıcı yaratma kapısıdır. Bakım uçlarını (`Sync*`) da
96
+ > kullanmayın; ürün yüzeyinin parçası değildir ve gateway onları araç olarak yayınlamaz.
112
97
 
113
98
  ## Middleware gerekli mi? (karar tablosu)
114
99
 
@@ -212,7 +212,9 @@ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözle
212
212
  Kurallar:
213
213
  - Gövde bir **dizi** olur; her eleman bir satırdır. `bulk-delete` gövdesinde yalnız birincil
214
214
  anahtar alanı aranır ve tek bir `IN` filtresine çevrilir.
215
- - `filters` **kullanılmaz** — satırlar ve anahtarlar gövdeden okunur.
215
+ - `bulk-create` `filters` **kullanmaz** — satırlar gövdeden okunur. `bulk-update`/`bulk-delete`'te `filters`
216
+ **kapsamdır**: gövdedeki kimlikler yalnız kapsamın içindeyse yazılır/silinir (aşağıdaki §Toplu yazmada sahiplik,
217
+ MS-027).
216
218
  - Tek istekte en fazla **1000 satır** (`EntityConstants.BulkRowLimit`); aşılırsa
217
219
  `BulkRowLimitException` (422).
218
220
  - Parti **hep-ya-hiç**tir: bir satır doğrulamadan geçmezse `BulkRowFailedException` hangi
@@ -404,7 +406,7 @@ KOŞUL belirleyemez, yalnız bir ANAHTAR belirler (yukarıdaki §"Güncelle/sil
404
406
 
405
407
  | Ne istiyorsun | Yol |
406
408
  |---|---|
407
- | Anahtarlar elinde, hepsi ya hep ya hiç yazılsın | **`bulk-update` ucu** — gövde satır dizisi, filtre yok, tek çağrı, tek transaction (yukarıdaki §"Toplu yazma"). En fazla 1000 satır; bir satır düşerse hiçbiri yazılmaz |
409
+ | Anahtarlar elinde, hepsi ya hep ya hiç yazılsın | **`bulk-update` ucu** — gövde satır dizisi, tek çağrı, tek transaction (aşağıdaki §Toplu yazmada sahiplik). En fazla 1000 satır; bir satır düşerse hiçbiri yazılmaz |
408
410
  | Satır başına SONUÇ ve satır başına YETKİ kararı gerekiyor | **İş akışı + döngü** (BK-05 deseni): liste ucunu çağır → her satır için `microservice` adımı → sayaçları topla → özet döndür |
409
411
 
410
412
  Kapsam ve politika koşulu her iki yolda da satır satır uygulanır; fark sonucun şeklidir. Toplu
@@ -413,6 +415,32 @@ onay gibi işlerde ikinci yol tercih edilir çünkü yetkisi yetmeyen satır öt
413
415
  akış "0 onaylandı · 2 reddedildi" döndürdü). Döngüdeki uç, çağıranın kimliğiyle denetlenir —
414
416
  akış içinden yazmak yetki YÜKSELTMEZ.
415
417
 
418
+ ### Toplu yazmada sahiplik (MS-027)
419
+
420
+ `bulk-update` ve `bulk-delete` ucunun `filters`'ı **kapsamdır**: gövdedeki her kimlik yalnız bu kapsamın içindeyse
421
+ yazılır ya da silinir (zeyrek-backend ADR 0074, 2026-11-04). Basit erişim modelinde kapsam olmadan uç, istemcinin
422
+ gönderdiği her kimliği — başkasının satırı dahil — yazar.
423
+
424
+ ```jsonc
425
+ "middlewareIds": ["<auth middleware id>"],
426
+ "sourceParameters": {
427
+ "operation": "bulk-update",
428
+ "fields": [{ "fieldSlug": "id", "source": "body" }, { "fieldSlug": "sira", "source": "body" }],
429
+ "filters": [{ "filterType": "single", "conditionType": "and", "fieldSlug": "sahip", "operator": "eq", "source": "expression", "value": "user.id" }]
430
+ }
431
+ ```
432
+
433
+ | Kural | Sonuç |
434
+ |---|---|
435
+ | Kapsam filtresi kök, `single`, `and`, `eq`; değeri `static` ya da `expression` (`user.id` gibi) | Geçer |
436
+ | Değer `body`/`header`/`query-string`'den ya da onları okuyan bir ifadeden (`body[0].sahip`, `header['x-…']`) geliyor · `eq` dışı işleç · grup · kapsam alanı `fields`'ta da var | Gateway MS-027 ile durdurur; platform `BulkWriteScopeFilterException` (400) |
437
+ | Filtre yok ya da `middlewareIds` boş | MS-027 uyarır; uç her kimliği yazar |
438
+ | Gövdede kapsam dışı tek kimlik | Bütün parti 404, hiçbir satır yazılmaz/silinmez |
439
+ | Kapsam ifadesi boş döner (`user` yok) | 404 |
440
+
441
+ Toplu silme önce sayım yapmaz; siler, eksik silinirse 404 döner ve istek geri alınır. Workflow `table` adımının
442
+ toplu dalları bu kuralı uygulamaz: satırları akış yazarının ifadesi seçer, istemciden gelen kimliği akış süzmelidir.
443
+
416
444
  ## İlişki alanı: `row_list` NESNE döner, uç/step JOIN'siz DÜZ FK döner
417
445
 
418
446
  Aynı alan iki yoldan farklı şekilde gelir ve bu fark sessizce yanlış sonuç üretir:
@@ -113,6 +113,9 @@ expression editörü ve senin keşif yolun budur; **değişken adını/alanını
113
113
  token'ı üretirken `system.generateToken({ … })`'a verdiğin sözlüğün anahtarlarıdır
114
114
  (`claims.userId`, `claims.email` …) — `token.data.id` diye evrensel bir alan YOKTUR.
115
115
 
116
+ Bu rehberdeki `start → X` okları başlangıç adımının dalıdır: `{ name: 'start', type: 'success', nextStepId: X }`.
117
+ Dal tipi `start` değildir; platform 400 döner (WF-038, `platform_guide('workflow')` §Step'ler nasıl bağlanır).
118
+
116
119
  ```
117
120
  start → token [code: return system.validateToken(cookie.<TokenCookie>)]
118
121
  token → valid? [condition: token.data.isValid && token.data.claims.<idAnahtarın>]
@@ -343,6 +346,83 @@ bırakıldığında akış `500 JavaScriptException: user is not defined` verdi.
343
346
  `platform_guide('microservice')` §"Kimlik kapsaması × uç kaynağı", bağlamanın yazım tuzağı
344
347
  `platform_guide('workflow')` §"Query adımı".
345
348
 
349
+ ## Tarif 4 — Şifre sıfırlama (kodla)
350
+
351
+ "Şifremi unuttum" bir middleware değil, iki **anonim** uçtur (`source: 'workflow'`, `middlewareIds: []`)
352
+ ve kullanıcı başına tek satırlık bir kod tablosudur. Hazır parçalar: `ngapps://examples/middleware-password-reset`
353
+ (tablo, dört DTO, iki akış, iki uç). Lokal yığında bu örnekten kurularak uçtan uca ölçüldü (2026-11-04).
354
+
355
+ **Tablo** `password_reset_codes`: `user_id` (UUID, **UNIQUE**), `salt`, `code_hash`, `expires_at`, `sent_at`,
356
+ `window_started_at`, `used_at` (TIMESTAMPTZ), `attempts` (INT4). Kullanıcı başına tek satır olduğu için yeni kod
357
+ satırı günceller ve önceki kod kendiliğinden geçersiz olur.
358
+
359
+ ```
360
+ sifre-kodu-iste { email }
361
+ start → kullanici [table get users, email eq input.email]
362
+ → kullaniciVar? ─ yok → sureEsitle ─────────────────────────────────┐
363
+ → mevcutKod [table get password_reset_codes, user_id] │
364
+ → hizSiniri? (sent_at 60 sn'den yeni ya da pencerede hak bitti) ─ evet → sureEsitle
365
+ → kodUret [code] → satirVar? ─ evet → kodGuncelle (used_at null; attempts/pencere taşınır)
366
+ └ hayır → kodYaz │
367
+ → eposta [email: to kullanici.data.email] (success ve fail) ───────────┤
368
+ yanit 200 { message: 'Kayıtlıysa e-posta gönderildi.' }
369
+
370
+ sifre-sifirla { email, code, newPassword }
371
+ start → parolaUygun? ─ hayır → 200 { ok: false, … }
372
+ → kullanici → kullaniciVar? ─ yok → sureEsitle → gecersiz
373
+ → kod [table get] → kodGecerli? (satır var · used_at boş · attempts < 5 · süresi dolmamış)
374
+ └ hayır → sureEsitle → gecersiz
375
+ → denemeSay [table update attempts + 1, filtre: id eq kod.data.id AND attempts eq kod.data.attempts]
376
+ → sayildi? (güncellenen satır döndü mü) ─ hayır → sureEsitle → gecersiz
377
+ → kodEslesiyor? ─ hayır → gecersiz 200 { ok: false, … }
378
+ → parolaYaz [table update users.password = input.newPassword]
379
+ → kodKullanildi (used_at = UtcNow()) → 200 { ok: true, … }
380
+ ```
381
+
382
+ - **Kod üretimi:** `GenerateGuid()`'in son 10 hex hanesi v7 GUID'in rastgele bitlerinden gelir:
383
+ `String(parseInt(hex.slice(-10), 16) % 1000000).padStart(6, '0')`. Saklanan değer
384
+ `crypto.SHA256.encode(salt + ':' + code)`, `salt` satıra özgü yeni bir GUID. Bu fonksiyon adına rağmen
385
+ PBKDF2'dir ve tuzu girdinin kendisidir (`platform_guide('expressions')`); tuz eklenmezse aynı 6 hane her
386
+ satırda aynı özeti verir.
387
+ - **Deneme hakkı karşılaştırmadan önce, karşılaştır-ve-yaz ile harcanır.** `denemeSay` sayacı yalnız okunduğu
388
+ değerdeyse artırır. Satırı Postgres kilitler. Aynı anda gelen öteki istek kilit açılınca koşulu yeniden
389
+ değerlendirir, satırı güncelleyemez ve adım boş döner; o istek kodu karşılaştırmadan reddedilir. Okuyup-artıran
390
+ bir adım (`attempts: kod.data.attempts + 1`, filtresiz) paralel isteklerde aynı sayacı okur ve hepsi karşılaştırma
391
+ yapar: 5 sınırı istek sayısı kadar aşılır. Ölçüm:
392
+ - Aynı anda 10 yanlış denemeden 2'si sayıldı, 8'i karşılaştırmadan reddedildi.
393
+ - Aynı doğru kodla aynı anda gelen 5 istekten tam 1'i `ok: true` aldı.
394
+ - **Sayaç yeni kodla sıfırlanmaz.** `window_started_at`'ten 24 saat geçmedikçe `attempts` taşınır; hak bitmişse yeni
395
+ kod da gönderilmez. Böylece hesap başına günde en çok 5 tahmin yapılır (10⁶'da 5). Her yeni kodla 5 hak açan
396
+ sürüm günde ~7 200 tahmine izin verirdi. Bedel: 5 kez yanlış yazan kullanıcı 24 saat bekler.
397
+ - **Sıfırla ucunun her sonucu 2xx döner.** Uç 4xx/5xx ile biterse platform işlemin bütün yazımlarını geri alır
398
+ (`platform_guide('workflow')` §Önizleme çıktısını okumak, "Yazmalar" satırı). 400 dönen ilk sürümde beş yanlış
399
+ denemeden sonra `attempts` 0 kaldı, altıncı denemede doğru kod kabul edildi. İstemci başarıyı `body.ok`'tan okur.
400
+ - **Hesap varlığı sızmaz:** kod iste ucu her durumda aynı gövdeyi döner. Kod karşılaştırılmayan her dal
401
+ `sureEsitle` (`crypto.SHA256.encode(GenerateGuid())`) adımından geçer, çünkü özet hesabı yanıt süresinin
402
+ büyük kısmıdır. Eşitlemesiz sürümde kayıtsız adres 9 ms'de, kayıtlı adres 130 ms'de döndü; eşitlemeden
403
+ sonra iki dal da 117–175 ms aralığında. E-posta gönderim süresi eşitlenmez; SMTP yavaşsa bu fark kalır.
404
+ - **Parola düz yazılır:** platform `users` tablosunun parola alanını yazarken özetler, zaten özetlenmiş
405
+ değere dokunmaz (`row_create` ve table adımı ikisi de). Elle `crypto.SHA256.encode` vermek de çift
406
+ özetlemez.
407
+ - **Hız sınırı:** aynı kullanıcıya 60 sn'de bir kod gider. Aynı anda gelen iki kod isteği eski `sent_at`'i birlikte
408
+ okuyup bu sınırı aşabilir; etkisi fazladan iletidir, sayaç ve pencere taşındığı için tahmin hakkı artmaz. Kod ve
409
+ özet yanıta girmez. `microservice_debug` adım çıktısında kodu gösterir; teşhis ucu olarak açık bırakma.
410
+ - **Açık oturumlar kapanmaz.** Basit projede uygulamanın kendi jetonu süresi dolana kadar geçerli kalır.
411
+ Kompleks projede platform parola değişiminde yenileme jetonu ailesini iptal etmez (PS-32, karar bekliyor).
412
+ Tarif bunu çözmez; jeton ömrünü kısa tut.
413
+ - **Sayfa:** giriş sayfasındaki "Şifremi unuttum" bağlantısı iki adımlı bir sayfaya gider: önce e-posta
414
+ formu (`sifre-kodu-iste`), sonra kod ve yeni parola formu (`sifre-sifirla`, `Steps.<adım>.data.ok`'a göre
415
+ bildirim). Sayfa da anonimdir (`middlewareIds` boş).
416
+
417
+ Doğrulama (lokalde ölçülen sıra), hepsi `microservice_execute(anonymous: true)` ile:
418
+
419
+ 1. Kod iste → 200 ve mailpit'te tek ileti (`curl -s 'localhost:8025/api/v1/search?query=to:<email>'`).
420
+ 2. Kayıtsız adres → aynı gövde, ileti yok.
421
+ 3. Aynı anda 10 yanlış kod → hepsi `ok: false`, `attempts` ≤ 5.
422
+ 4. Sayaç 5'e varınca doğru kod da `ok: false`. Yeni kod istenirse ileti gitmez.
423
+ 5. Pencere dolunca yeni kod gelir. Aynı anda 5 doğru kod → tam bir `ok: true`.
424
+ 6. Giriş ucu yeni parolayla 200, eskisiyle 401.
425
+
346
426
  ## Tuzaklar
347
427
 
348
428
  - Expression'daki değişken adı, bağlı middleware'lerden birinin kurduğu adla **birebir**
@@ -368,9 +448,9 @@ Middleware bağlamadan önce `users` tablosunda satır olduğundan emin ol — `
368
448
  kapı bağlanınca her istek 401 döner ve bu ancak sayfa boş açılınca fark edilir (09-02 ve 09-04
369
449
  koşularında ikisinde de yaşandı).
370
450
 
371
- İlk kullanıcıyı açmanın desteklenen tek yolu bootstrap desenidir:
372
- `platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?". Kısa yol arama — bakım uçları
373
- (`Sync*`) ürün yüzeyinin parçası değildir ve gateway onları araç olarak yayınlamaz.
451
+ İlk kullanıcıyı `row_create` ile `users`'a yazın; platform parolayı özetler:
452
+ `platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?". Bakım uçları (`Sync*`) ürün yüzeyinin parçası
453
+ değildir ve gateway onları araç olarak yayınlamaz.
374
454
 
375
455
  ## Korumalı bir ucu TEST ederken: token ÇEREZDEN okunur
376
456
 
@@ -106,9 +106,19 @@ Bir ekran yukarıdan aşağıya şu sırayla kurulur: **başlık → bölümler
106
106
 
107
107
  ## Boşluk
108
108
 
109
- Aralığın tek kaynağı ebeveynin `gap`'idir. Runtime'ın varsayılanları katmanlarda birikir: margin tipi
110
- yazılmamış her düğüm 4 dp, bırakılabilir düğüm ayrıca 10 dp padding alır. İki kat iç içe zeminsiz kap, içeriği
111
- 28 dp kaydırır.
109
+ Aralığın tek kaynağı ebeveynin `gap`'idir. Runtime'ın varsayılanları katmanlarda birikir. İki kat iç içe zeminsiz
110
+ kap, içeriği 28 dp kaydırır. Varsayılanlar (boşluk tipi yazılmamışsa; açık yazılan değer her satırda uygulanır):
111
+
112
+ | Düğüm | Dış boşluk | İç boşluk | Not |
113
+ |---|---|---|---|
114
+ | Margin tipi yazılmamış düğüm | 4 dp | — | runtime `RUNTIME_NORMAL_MARGIN` |
115
+ | Bırakılabilir düğüm (çocuk alan kap) | 4 dp | 10 dp | `RUNTIME_NORMAL_PADDING`; `isWrapper` dış boşluğu sıfırlar, iç boşluğu sıfırlamaz |
116
+ | `safe-area`, `scroll-view`, `app-bar`, `tooltip`, `view` | 0 | 0 | ekran kenarına yaslanan kaplar (`FLUSH_SPACING_TYPES`) |
117
+ | `auth-layout`, `modal`, `drawer` | 0 | 0 | katmanlar; `modal`/`drawer` paneli temanın `spacing.md` iç boşluğunu kendisi verir |
118
+
119
+ Çocuklar arası: `container` ve `scroll-view` `gap` varsayılanı `medium` = 16 dp (ölçek `small` 8 · `medium` 16 ·
120
+ `large` 24). Mobil `repeater` `gap` okumaz: satır arası `verticalSpacing`, sütun arası `horizontalSpacing`
121
+ (şema varsayılanı 12 / 12). `carousel` `gap` 0'dır (px).
112
122
 
113
123
  - **Gateway yeni düğüme `marginType: 'none'` yazar** (`page_create`, `page_schema_patch`,
114
124
  `page_schema_validate`) ve bunu `gatewayNormalized`'da söyler.
@@ -130,6 +140,10 @@ yazılmamış her düğüm 4 dp, bırakılabilir düğüm ayrıca 10 dp padding
130
140
  sağ kenar hizadan kopar (MOB-020).
131
141
  - **Yönü yazılmamış `container` ve `form` satırdır.** Alanlar yan yana sıkışır; `direction: 'column'` yazın.
132
142
  `safe-area` ve `scroll-view` sütun doğar.
143
+ - **Satırda üç ya da daha çok çocuk dar sütunlara sıkışır** (MOB-043): `direction: 'row'` ve sarmasız kapta üç ya da
144
+ daha çok çocuk yan yana, en az ikisi `fill` ise önizlemede de telefonda da daralırlar (2 alan + düğme: her biri ~106 dp).
145
+ Alt alta için `direction: 'column'`. İki `fill` çocuk eşit paylaşır ([İptal | Kaydet], Ad | Soyad) ve bir `fill` alan
146
+ + düğme (arama satırı) bilinçli satırdır, uyarı almaz.
133
147
  - **Ortalanmış ekran** (giriş, boş durum) için sütuna `horizontalAlign: 'center'` verin. Bu kapsamda kenar
134
148
  eşitliği aranmaz.
135
149
 
@@ -162,6 +176,24 @@ Gerekçesiz ya da iç içe dikey kaydırıcı jestleri çakıştırır (MOB-016)
162
176
  boyunca eşit böler ve kısa içerik üst üste biner. Yapışkan başlık ve ekrana sabit içerik için
163
177
  `platform_guide('mobile')` §Ekrana sabit içerik.
164
178
 
179
+ ## Klavye
180
+
181
+ Klavye açılınca yazılan alan görünür kalır; tasarıma iş düşmez. Kabuk 0.12.0 ve sonrası:
182
+
183
+ - **Kaydırılan sayfa** kendi payını açar ve odaktaki satırı klavyenin üstüne kaydırır.
184
+ - **Sabit boylu sayfa** (kaydırma bir `scroll-view`'da) klavye kadar kısalır.
185
+ - **Alta çapalı katman** (`fab`, `floating-container` `bottom-*`) klavyenin tepesine kalkar; orta/üst çapalı katman
186
+ yerinde kalır.
187
+ - **Sekme çubuğu** klavye açıkken görünür kalır. Gizlemek için çubuğa `"hideOnKeyboard": true`
188
+ (`platform_guide('mobile')` §Gezinme).
189
+ - **Kullanıcının koyduğu `scroll-view`** (0.12.0'dan SONRAKİ kabuk; 0.12.0'da yok): klavye açıkken içindeki düğmeye ilk dokunuş düğmeye gider (önce klavyeyi
190
+ kapatmakla harcanmaz). Kaydırıcı kendi klavye payını açmaz; sabit boylu sayfa onu zaten kısaltır. Bilinen sınır:
191
+ kısalan `scroll-view`'un altında kalan alan odaklanınca kendiliğinden görünür alana gelmez, kullanıcı kaydırır.
192
+ Gönder düğmesini formun üstüne ya da alta çapalı katmana koyun.
193
+
194
+ 0.12.0'dan eski kabukta sayfa payı yoktur: alt yarıdaki alan klavyenin altında kalır. Geçici çözüm, formu ve gönder
195
+ düğmesini ekranın üst yarısına almaktır. Klavye açıkken yazılan hâli önizlemede sınamak: `set` + `blur: false`.
196
+
165
197
  ## Doğrulama
166
198
 
167
199
  Bir ekranı bitirince iki çağrı yapın:
@@ -185,6 +185,36 @@ yazılmayan soru boş kalır).
185
185
  Cevap uygulamanın içeriğinden çıkar (sayfalar, akışlar, reklam ayarı: `settings.ads.enabled` → `advertising`);
186
186
  emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldadır (insan).
187
187
 
188
+ ## Kabuk sürümleri
189
+
190
+ Telefondaki uygulama, derlemenin aldığı **kabuk paketinin** taşıdığı özellikleri çalıştırır. Yayındaki kabuğun sürümünü
191
+ ve her yeteneği `mobile_store_get`'in `shell` alanı söyler (`shell.capabilities.<ad>: { carried, since }`; kaynak
192
+ `GET mobile-store/shell`, tarama yapmaz). Bir geçici
193
+ çözümü kaldırmadan önce oradan okuyun; rehberin anlattığı davranış en yeni kabuğundur. Tablonun kaynağı backend'dir
194
+ (`MobileBuildOptions.ShellCapabilities`); bu tabloyla eşliğini bir test denetler.
195
+
196
+ | Yetenek | En düşük kabuk | Rehberdeki yeri | Eski kabukta |
197
+ |---|---|---|---|
198
+ | `push` | 0.1.0 | §Ön kontrol P-11 | derleme push'suz çıkar (`push_shell_outdated`) |
199
+ | `permissions` | 0.2.0 | §İzinler | sabit izinler bildirilir, kipler uygulanmaz (P-12) |
200
+ | `launchPermissions` | 0.3.0 | §İzinler | açılışta izin sorulmaz (P-12) |
201
+ | `ads` | 0.4.0 | §Reklamlar | derleme reklamsız çıkar (P-13) |
202
+ | `scan` | 0.5.0 | `platform_guide('mobile-steps')` `scan-code` | adım `error` dalıyla biter (P-14) |
203
+ | `appLock` | 0.6.0 | §Uygulama kilidi | uygulama kilitsiz açılır (P-12) |
204
+ | `offlineSeed` | 0.7.0 | `platform_guide('page')` §Kural 1g-4 | derlemede ekran kopyası yok: ilk açılış ağ ister |
205
+ | `nonProductionEnvironments` | 0.7.0 | §Test derlemesi | production dışı ortam açılmaz; ön kontrol P-32 `fail`, derleme başlamaz |
206
+ | `purchases` | 0.9.0 | §Satın almalar | satın alma adımları `unsupported` döner |
207
+ | `purchasesSilentRestore` | 0.10.0 | §Satın almalar | açılışta sessiz geri yükleme yapılmaz |
208
+ | `optionalStoreUrls` | 0.12.0 | §Ön kontrol P-33 | boş gizlilik ya da destek adresiyle açılmaz; ön kontrol P-33 `fail` |
209
+ | `backStep` | 0.12.0 | `platform_guide('mobile')` `back` adımı | `back` adımı yok: önceki sayfaya `redirect` |
210
+ | `tabRedirectReturns` | 0.12.0 | `platform_guide('mobile')` sekmeye `redirect` | sekme sayfasına `redirect` yığına kopya iter |
211
+ | `keyboardInsets` | 0.12.0 | `platform_guide('mobile')` | klavye alttaki alanı örter: gönder düğmesini ekranın üst yarısına alın |
212
+ | `flowDrain` | 0.12.0 | `platform_guide('mobile-steps')` §Sayfa kapanınca | sayfa kapanınca akış yarıda kalır: isteği göndermeden ekranı değiştirmeyin |
213
+ | `sharedAppState` | 0.12.0 | `platform_guide('mobile')` §Kalıcı state | her sayfa kendi `state.app` kopyasını alır: sayfalar arası değeri saklama türüyle taşıyın |
214
+ | `offlineQueueEvent` | 0.12.0 | `platform_guide('page')` §Kural 1g-4 | `offlineQueueChange` koşmaz: kuyruğu bir zamanlayıcıyla yeniden okuyun |
215
+
216
+ Lokal yığının derleme varsayılanı daha eski olabilir; telefondaki sürümü tahmin etmeyin, `shell.version`'dan okuyun.
217
+
188
218
  ## Ön kontrol
189
219
 
190
220
  `mobile_build_start { dryRun: true }` yalnız `ok` OLMAYAN satırları döner (`okCount` gerisini sayar). Tablo
@@ -194,7 +224,7 @@ emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldad
194
224
  |---|---|---|
195
225
  | P-01 | `settings_incomplete`; Android `internal`'da `release_notes_too_long` (bir dilde `releaseNotes` > 500 — Play sınırı; ayar 4000'e izin verir çünkü App Store'a da gider; ikisi birlikteyse tek satırda iki cümle). Mesaj alanları istek dilinde adlandırır ("başlık (English)", "gizlilik politikası adresi"), ayar yolunu değil — yol `mobile_store_get`'ten | `mobile_store_update` |
196
226
  | P-02 | `assets_missing` | `mobile_asset_create` |
197
- | P-03 | `secrets_missing`, `secrets_unreadable`, `signing_expired` (`warn signing_expiring`) | Studio › Yayın › Hesaplar |
227
+ | P-03 | `secrets_missing`, `secrets_unreadable`, `signing_expired` (`warn signing_expiring`); iOS'ta sertifika Apple hesabının dağıtım sertifikaları arasında yoksa `signing_certificate_revoked` (iptal edilmiş ya da silinmiş; platformun açtığı sertifika Apple kimliğiyle, yüklenen sertifika baştaki sıfırları atılmış seri numarasıyla eşlenir), App Store Connect'e sorulamazsa `warn signing_revocation_unverified` (derlemeyi engellemez) | Studio › Yayın › Hesaplar |
198
228
  | P-04 | `apple_account_missing`, `asc_bundle_id_missing`, `asc_app_missing`, `asc_unauthorized`, `asc_forbidden`, `asc_unreachable` | `asc_bundle_id_missing`: `mobile_store_update { registerAppleBundleId: true }`. `asc_app_missing`: App Store Connect'te uygulama kaydı (API açamaz, insan açar). Diğerleri: Studio hesabı |
199
229
  | P-05 | `google_account_missing`, `play_app_missing`, `play_unauthorized`, `play_forbidden`, `play_unreachable` | Play Console'da uygulama + servis hesabı yetkisi |
200
230
  | P-06 | `production_missing`, `production_page_missing` (yalın `build`'de `warn`, başlatmayı engellemez); production dışı `environment` ile `fail environment_page_missing` | production ortamı ve dağıtım — insan onaylı, ajan yapmaz; başka ortamda açılış sayfası o ortamda |
@@ -214,6 +244,8 @@ emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldad
214
244
  | P-20 | kendi kaydı olan uygulamada hesap silme yolu görülmezse `manual account_deletion_unverified` (5.1.1(v)) | ekran + kullanıcı doğrular |
215
245
  | P-21 | `age_rating_missing` (yaş derecelendirmesi yanıtsız) | `settings.apple.advisory` |
216
246
  | P-22 | yayın hedefinde `release_source_missing`: kaynak derleme yok (`testflight` → `app-store-review` → `app-store-release`, `internal` → `play-promote`) | önce kaynak hedefi derle |
247
+ | P-32 | production dışı `environment` ile derlemede kabuk o ortamı açamıyorsa `shell_environment_unsupported` (`nonProductionEnvironments`, 0.7.0); açabiliyorsa `ok shell_environment_ready`. Ön kontrol atlanırsa `mobile_build_start` aynı koşulda 422 `MobileBuildShellLacksCapabilityException` döner | production'dan derleyin ya da daha yeni kabuk (destek) |
248
+ | P-33 | kabuk `optionalStoreUrls` taşımıyorsa (0.12.0'dan eski) ve boş mağaza adresi varsa `store_urls_shell_required` — kabuk boş adresle açılmaz; ileti boş alanları sayar: destek adresi her derlemede, gizlilik adresi mağaza dışı derlemede (mağaza yüklemesinde onu P-01 / P-19 sorar). 0.12.0+ kabukta satır yok | `settings.urls.support` / `settings.urls.privacyPolicy` ya da daha yeni kabuk |
217
249
 
218
250
  `manual` satır başlatmayı engellemez ama kullanıcıya söylenir; `fail` engeller. Satırın `message` ve `fixHint`'i
219
251
  backend'den, istek dilinde gelir.
@@ -36,7 +36,8 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
36
36
 
37
37
  ## Sayfa kapanınca
38
38
 
39
- Sayfa söküldüğünde (geri, yığının köke inmesi, çıkış — sekme değişimi sayfayı sökmez) açık akış yarıda kalmaz (kök ADR 0030): yeni koşum başlamaz, açık
39
+ Kabuk 0.12.0+ (`flowDrain`; daha eski kabukta akış sayfayla kesilir — isteği göndermeden ekranı değiştirmeyin; yayındaki
40
+ sürüm `mobile_store_get` `shell`). Sayfa söküldüğünde (geri, yığının köke inmesi, çıkış — sekme değişimi sayfayı sökmez) açık akış yarıda kalmaz (kök ADR 0030): yeni koşum başlamaz, açık
40
41
  koşumlar telefona yazan adımlarla sonuna kadar sürer. 30 sn'de bitmeyen koşum kapatılır.
41
42
 
42
43
  | Adımlar | Sayfa kapalıyken |
@@ -48,7 +49,7 @@ koşumlar telefona yazan adımlarla sonuna kadar sürer. 30 sn'de bitmeyen koşu
48
49
 
49
50
  Atlanan her adım telefonun defterine `page-unmounted` kaydı düşer (`mobile_error_list`). `state.*` yazımı `storage` ya da
50
51
  `code` adımıyla yapılır — `setter` atlanır. "İsteği göndermeden önce ekranı değiştir", "önce yaz sonra çık" gibi geçici
51
- çözümler gerekmez. Studio mobil önizlemesi ve `/view` de sayfa geçişinde boşaltır; "Dene"nin yeniden başlatması ve
52
+ çözümler 0.12.0+ kabukta gerekmez. Studio mobil önizlemesi ve `/view` de sayfa geçişinde boşaltır; "Dene"nin yeniden başlatması ve
52
53
  önizlemenin kapanması akışı keser. Web (Svelte) uygulaması akışı sayfayla birlikte keser.
53
54
 
54
55
  ## Önizlemede çekim hedefleri
@@ -71,6 +72,15 @@ rollerini sayar):
71
72
 
72
73
  Pencere açıkken altındaki sayfa öğesine `click` uygulanmaz (SCR-023).
73
74
 
75
+ **`click` gerçek noktaya basar** (SCR-024). Hedef sırayla seçilir: kök basılabilirse kendisi, sonra `<id>__press`, sonra
76
+ içteki ilk düğme. Basış hedefin görünen bir noktasına gerçek fare olayıyla yapılır: önce orta; ortada hedefin içindeki
77
+ başka basılabilir öğe (kabın düğmesi, çipin "kaldır"ı) varsa kutunun başka bir noktası. Görünmeyen hedef önce görünüme
78
+ kaydırılır. Hiçbir noktası hedefin değilse eylem uygulanmaz: `applied: false`, `coveredBy: '<örten elementId>'`.
79
+ Telefonda da dokunuşu o öğe alır. Örten şema düğümü değilse (kabuğun bildirim katmanı, açık kalmış toast) `coveredBy`
80
+ rolünü ve metnini söyler: `div[role=alert] "…"`. Örten bir `stack` katmanı ya da yerleşim kabıysa boş alanı dokunuşu
81
+ geçirmeli; bilerek üstteyse onu hedefleyin. Kutusu boş hedefte not "görünmüyor" der. `flowRuns` biten akışı sayar:
82
+ `alert`/`confirm` adımında bekleyen akış, pencereyi yanıtlayan eylemin raporunda sayılır.
83
+
74
84
  ## Bildirim tarifi
75
85
 
76
86
  Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce