@appaflytech/zeyrek-ai-mcp 0.0.43 → 0.0.45

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 (65) hide show
  1. package/dist/knowledge/backendConstraints.js +86 -2
  2. package/dist/knowledge/componentCatalog.js +63 -26
  3. package/dist/knowledge/componentEvents.js +6 -0
  4. package/dist/knowledge/connectDataBind.js +43 -6
  5. package/dist/knowledge/connectDataDefinition.js +74 -3
  6. package/dist/knowledge/connectDataRules.js +14 -0
  7. package/dist/knowledge/mobileComponentCatalog.js +66 -7
  8. package/dist/knowledge/mobileStore.js +61 -2
  9. package/dist/knowledge/platform/mobile.js +1 -1
  10. package/dist/knowledge/platform/mobileRules.js +2 -1
  11. package/dist/knowledge/platform/mobileTabs.js +31 -2
  12. package/dist/knowledge/platform/web.js +1 -1
  13. package/dist/knowledge/rules.js +195 -4
  14. package/dist/knowledge/textHooks.js +434 -0
  15. package/dist/platform/capture/capture.js +1 -1
  16. package/dist/platform/capture/readiness.js +4 -0
  17. package/dist/platform/errors.js +22 -2
  18. package/dist/prompts/mobilePublish.js +21 -0
  19. package/dist/resources/examples/flow-biometric-confirm.json +199 -0
  20. package/dist/resources/examples/microfunction-node-dependencies.json +19 -0
  21. package/dist/resources/guides/access.md +3 -3
  22. package/dist/resources/guides/component.md +20 -4
  23. package/dist/resources/guides/deployment.md +98 -0
  24. package/dist/resources/guides/entity.md +1 -1
  25. package/dist/resources/guides/expressions.md +5 -1
  26. package/dist/resources/guides/external-component.md +6 -4
  27. package/dist/resources/guides/merge.md +81 -4
  28. package/dist/resources/guides/microfunction.md +104 -9
  29. package/dist/resources/guides/microservice.md +34 -5
  30. package/dist/resources/guides/mobile-connect-data.md +70 -0
  31. package/dist/resources/guides/mobile-flow.md +64 -0
  32. package/dist/resources/guides/mobile-publish.md +161 -16
  33. package/dist/resources/guides/mobile-steps.md +17 -0
  34. package/dist/resources/guides/mobile.md +41 -23
  35. package/dist/resources/guides/page.md +11 -5
  36. package/dist/resources/guides/translation.md +95 -0
  37. package/dist/resources/knowledge.js +3 -1
  38. package/dist/resources/prompts/mobile-publish-prepare.md +89 -0
  39. package/dist/resources/schema-fingerprint.json +4 -4
  40. package/dist/resources/schemas/ApplyMicrofunctionArchiveChangesCommand.json +76 -0
  41. package/dist/resources/schemas/ConnectDataDescribeDto.json +44 -0
  42. package/dist/resources/schemas/ConnectDataResultDto.json +10 -0
  43. package/dist/resources/schemas/CreateMicrofunctionCommand.json +8 -0
  44. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +0 -4
  45. package/dist/resources/schemas/MobileStorePreflightScope.json +13 -0
  46. package/dist/resources/schemas/StartMobileBuildCommand.json +35 -1
  47. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +8 -0
  48. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +75 -0
  49. package/dist/server.js +2 -0
  50. package/dist/tools/common.js +6 -0
  51. package/dist/tools/components.js +19 -6
  52. package/dist/tools/connectData.js +9 -4
  53. package/dist/tools/deployments.js +1 -1
  54. package/dist/tools/designSchemaPatch.js +16 -4
  55. package/dist/tools/environments.js +3 -3
  56. package/dist/tools/files.js +1 -1
  57. package/dist/tools/flows.js +29 -3
  58. package/dist/tools/layouts.js +32 -12
  59. package/dist/tools/merges.js +8 -8
  60. package/dist/tools/microfunctions.js +53 -3
  61. package/dist/tools/mobileStore.js +83 -21
  62. package/dist/tools/pages.js +59 -24
  63. package/dist/tools/platformInfo.js +4 -2
  64. package/dist/tools/textHookGuard.js +116 -0
  65. package/package.json +2 -2
@@ -7,19 +7,30 @@ import { PlatformApiError } from '../platform/errors.js';
7
7
  import { handle } from '../platform/responses.js';
8
8
  import { attachFindings, enforcePolicy, evaluatePolicy } from '../knowledge/policy.js';
9
9
  import { policyRules } from '../knowledge/rules.js';
10
- import { ASSET_KINDS, BUILD_ASSET_KINDS, DECLARATION_KEYS, DEVICE_CLASSES, MAX_ASSET_BYTES, ORIENTATIONS, RELEASE_STATUSES, SCREENSHOT_CAPTURE_DEVICES, SPLASH_RESIZE_MODES, USER_INTERFACE_STYLES, adsStoreWarnings, trackingDeclarationWarnings, assetSlotProblem, changedStoreFields, mergeStoreSettings, permissionLaunchShellWarnings, permissionStoreBuildWarnings } from '../knowledge/mobileStore.js';
10
+ import { APPLE_RELEASE_TYPES, ASSET_KINDS, BUILD_ASSET_KINDS, DECLARATION_KEYS, DEVICE_CLASSES, MAX_ASSET_BYTES, ORIENTATIONS, PLAY_TRACKS, RELEASE_STATUSES, SCREENSHOT_CAPTURE_DEVICES, SPLASH_RESIZE_MODES, USER_INTERFACE_STYLES, adsStoreWarnings, trackingDeclarationWarnings, assetSlotProblem, changedStoreFields, mergeStoreSettings, permissionLaunchShellWarnings, permissionLockShellWarnings, permissionStoreBuildWarnings } from '../knowledge/mobileStore.js';
11
11
  /**
12
12
  * Mobil mağaza yayınının ajan yolu (`plans/mobil-magaza-yayini` g-11, `plans/mobil-yayin-mcp` g-01):
13
13
  * gizli olmayan mağaza ayarı, mağaza görselleri, beyanlar ve derleme işi. Gizli bilgi (Apple/Google
14
14
  * hesabı, sertifika, keystore, profil, demo parolası) ve runner kaydı bilinçli olarak YOK — Studio ›
15
15
  * Yayın'dan insan yapar (`docs/adr/0002-mobil-yayin-gorsel-ve-beyan.md`). Uçlar Core DB'dedir (ortam
16
- * kavramı yok); derlenen uygulama her zaman production ortamını okur.
16
+ * kavramı yok); derlenen uygulama işin ortamını okur — boşsa production (backend ADR 0032).
17
17
  */
18
18
  const applicationId = guid.describe("Mobil uygulamanın id'si (application_list / mobile_app_create çıktısı). Slug verilirse DEFAULT_PROJECT içinde çözülür");
19
19
  const buildId = guid.describe("Derleme işinin id'si (mobile_build_start ya da mobile_build_status { applicationId } çıktısı)");
20
20
  const mobilePlatform = z.enum(['ios', 'android']);
21
- const buildTarget = z.enum(['build', 'testflight', 'internal'])
22
- .describe("build: yalnız derler (imzasız/imzalı çıktı, mağazaya gitmez) · testflight: iOS'ta TestFlight'a yükler · internal: Android'de Play iç test kanalına yükler. Varsayılan build");
21
+ /**
22
+ * Mağaza yayın hedefleri (b-24): önceki hedefin derlemesini incelemeye/mağazaya/kanala taşır. Geri dönüşsüz ya da
23
+ * insan onaylıdır; MCP yalnız ön kontrolünü okur (`docs/adr/0002-mobil-yayin-gorsel-ve-beyan.md` §Ek 2026-10-10).
24
+ */
25
+ const RELEASE_TARGETS = ['app-store-review', 'app-store-release', 'play-promote'];
26
+ /**
27
+ * Ön kontrolün kapsamı (backend ADR 0032): `store` mağazaya giden işin bütün kuralları, `test` yalnız TestFlight / Play iç
28
+ * test yüklemesinin istedikleri. Ortam yalnız `test` ile seçilir; yükseltme hedefi kaynağı production işinden taşır.
29
+ */
30
+ const PREFLIGHT_SCOPES = ['store', 'test'];
31
+ const PLATFORM_TARGETS = { ios: ['build', 'testflight', 'app-store-review', 'app-store-release'], android: ['build', 'internal', 'play-promote'] };
32
+ const buildTarget = z.enum(['build', 'testflight', 'internal', ...RELEASE_TARGETS])
33
+ .describe("build: yalnız derler (mağazaya gitmez) · testflight: iOS'ta TestFlight'a yükler · internal: Android'de Play iç test kanalına yükler · app-store-review (iOS, son TestFlight derlemesini incelemeye gönderir) · app-store-release (iOS, onaylanan sürümü yayınlar) · play-promote (Android, iç test derlemesini releaseTrack kanalına taşır) — bu üçü YALNIZ dryRun ile: gerçek gönderim Studio › Yayınla'da insan onayıyla. Varsayılan build");
23
34
  const listingSchema = z.object({
24
35
  title: z.string().optional().describe('≤ 30'),
25
36
  subtitle: z.string().optional().describe('≤ 30 (iOS)'),
@@ -46,6 +57,8 @@ const settingsPatch = z.object({
46
57
  permissions: z.record(z.string().nullable()).optional().describe('Modül → izin açıklaması (1–500); anahtar ^[a-z][A-Za-z0-9]*$ (ör. camera)'),
47
58
  permissionModes: z.record(z.string()).optional().describe("İzin anahtarı → mağaza derlemesindeki kip: auto (uygulama kullanıyorsa dahil; varsayılan) | on (her zaman dahil) | off (dahil etme — o izni kullanan ekran 'unavailable' alır, production kullanıyorsa ön kontrol P-12 derlemeyi durdurur). Yalnız çıkarılabilir izinler yazılır (backend 422); anahtarlar ve bugünkü durum: mobile_store_get { detail: ['permissions'] }. Yama kayıttaki kiplerin üstüne birleşir"),
48
59
  launchPermissions: z.array(z.string()).optional().describe("Uygulama açılınca bir kez sorulacak izinlerin anahtarları (mağaza sürümünde; yalnız hiç sorulmamışsa sorulur; soruş sırası dizinin değil kataloğun sırasıdır). Liste BÜTÜN olarak yazılır: verilen dizi kayıttakinin yerine geçer, [] hepsini kaldırır. Açılışta sormak bir kullanımdır: izin derlemeye girer ve bir sonraki mağaza derlemesiyle etkili olur. Kipi off olan izin listeye yazılamaz (derlemenin taşımadığı izin sorulamaz): önce permissionModes.<key> auto ya da on yapılır. Anahtarlar: mobile_store_get { detail: ['permissions'] }"),
60
+ notificationReminder: z.enum(['off', 'daily']).optional().describe("Mağaza kabuğunda bildirim izni hatırlatması (kök K11): off (varsayılan) ya da daily — izin verilmemiş kullanıcıya uygulama öne gelince günde EN ÇOK BİR kez ince bir şerit çıkar. Manifest alanı yalnız daily için yazılır ve BİR SONRAKİ MAĞAZA SÜRÜMÜYLE etkili olur; push kapalıysa şerit hiç çıkmaz"),
61
+ releaseTargets: z.object({ ios: z.boolean(), android: z.boolean() }).strict().optional().describe("Bu sürümün gittiği mağazalar (kök K15). İKİSİ DE false kaydedilemez (422 releaseTargets). Kapalı platformun ön kontrolü tek satır 'skipped' döner (P-00 platform_disabled) ve o platformda iş başlatmak 422 verir: önce mağazayı açın"),
49
62
  listing: z.record(listingSchema).optional().describe("Dil kodu ('tr', 'en-US') → mağaza metni; verilen dil alan alan birleşir. Dil SİLİNMEZ (alanları '' yapılır; dili kaldırmak Studio › Yayın)"),
50
63
  urls: z.object({
51
64
  support: z.string().optional(),
@@ -57,12 +70,21 @@ const settingsPatch = z.object({
57
70
  ascAppId: z.string().optional(),
58
71
  categories: z.object({ primary: z.string().optional(), secondary: z.string().optional() }).strict().optional(),
59
72
  advisory: z.record(z.string()).optional(),
60
- copyright: z.string().optional().describe('≤ 200; TestFlight yüklemesinde zorunlu')
73
+ copyright: z.string().optional().describe('≤ 200; TestFlight yüklemesinde zorunlu'),
74
+ releaseType: z.enum(APPLE_RELEASE_TYPES).optional().describe("Onaylanan sürüm ne zaman yayına çıkar: manual (varsayılan; Studio'da 'Mağazada yayınla' ile) · automatic (onay anında, geri dönüşsüz) · scheduled (scheduledAt'te)"),
75
+ scheduledAt: z.string().nullable().optional().describe("releaseType 'scheduled' için ileri tarih (ISO 8601, UTC)"),
76
+ phasedRelease: z.boolean().optional().describe('true: otomatik güncellemeler 7 güne yayılır (kademeli yayın)'),
77
+ pricing: z.string().nullable().optional().describe("'free' ya da Apple fiyat basamağı tier-1…tier-87"),
78
+ territories: z.array(z.string()).nullable().optional().describe("Satış ülkeleri: tek başına ['all'] ya da ISO 3166 alfa-3 kodları (['TUR', 'DEU']); liste bütün olarak yazılır. Gönderim ülkeleri yalnız EKLER — satıştan kaldırma App Store Connect'te")
61
79
  }).strict().optional(),
62
80
  play: z.object({
63
81
  developerId: z.string().optional(),
64
82
  contactEmail: z.string().optional().describe('Play yüklemesinde zorunlu'),
65
- releaseStatus: z.enum(RELEASE_STATUSES).optional().describe('Play uygulaması taslakken yalnız draft kabul edilir')
83
+ releaseStatus: z.enum(RELEASE_STATUSES).optional().describe('Play uygulaması taslakken yalnız draft kabul edilir'),
84
+ contactPhone: z.string().nullable().optional().describe('Mağaza iletişim telefonu, E.164 (+905551112233)'),
85
+ contactWebsite: z.string().nullable().optional().describe('Mağaza iletişim sitesi, mutlak https://'),
86
+ promoVideoUrl: z.string().nullable().optional().describe('Tanıtım videosu: https:// YouTube adresi'),
87
+ rolloutPercent: z.number().int().min(1).max(100).nullable().optional().describe('Üretim kanalına kademeli yayın yüzdesi (1–100; boş = 100, hepsine)')
66
88
  }).strict().optional(),
67
89
  review: z.object({
68
90
  firstName: z.string().optional(),
@@ -84,14 +106,18 @@ const settingsPatch = z.object({
84
106
  ios: z.string().nullable().optional().describe('iOS reklam birimi: ca-app-pub-<16 hane>/<10 hane>'),
85
107
  android: z.string().nullable().optional().describe('Android reklam birimi: ca-app-pub-<16 hane>/<10 hane>')
86
108
  }).strict()).optional().describe('Adlı reklam yerleşimleri (≤ 40); liste bütün olarak yazılır')
87
- }).strict().optional().describe('Reklamlar (kök ADR 0012): açıkken izleme izni (tracking) derlemeye girer; manifest kabuk 0.4.0 ister')
109
+ }).strict().optional().describe('Reklamlar (kök ADR 0012): açıkken izleme izni (tracking) derlemeye girer; manifest kabuk 0.4.0 ister'),
110
+ appLock: z.object({
111
+ enabled: z.boolean().optional(),
112
+ timeoutSeconds: z.number().int().optional().describe('Arka plandan dönünce ne kadar sonra kilitlensin: 0 (hemen) · 60 · 300 · 900 saniye; kapalı küme, dışı 422')
113
+ }).strict().optional().describe('Uygulama kilidi (kök ADR 0021): açıkken mağaza sürümü soğuk açılışta ve arka planda timeoutSeconds süresinden uzun kalıp dönünce (0: her dönüşte) Face ID / parola ister ve biometric izni derlemeye girer. permissionModes.biometric off iken açılamaz (422). Manifest kabuk 0.6.0 ister; oynatıcıda kilit yoktur')
88
114
  }).strict();
89
115
  /** Backend bir çağrıda en çok bu kadar satır döner (`MobileBuildService.LogPageLimit`); dolu sayfa = devamı var */
90
116
  const LOG_PAGE_LIMIT = 2000;
91
117
  const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
92
118
  // Bölüm ve alan adları `secretKeyPattern`'e (credential|secret|token…) takılmamalı: takılan anahtarın
93
119
  // değeri yanıtta `***` olur (GW-001). Bu yüzden kimlik bilgisi bölümü `signing`, olay türü `subject`.
94
- const DETAIL_SECTIONS = ['assets', 'signing', 'runners', 'audit', 'permissions'];
120
+ const DETAIL_SECTIONS = ['assets', 'signing', 'runners', 'audit', 'permissions', 'status', 'changes'];
95
121
  /** Son kaç denetim kaydı döner; Studio aynı listeyi sayfalamadan gösterir, ajanın son olaylar yeter */
96
122
  const AUDIT_LIMIT = 20;
97
123
  /** İzin başına dönen kullanım yeri; toplam `usageCount`'tadır (bir izni 200 sayfa kullanabilir) */
@@ -155,7 +181,7 @@ async function readDetail(platform, appId, section, scope) {
155
181
  });
156
182
  const counts = !(devices instanceof PlatformApiError) && isObject(devices.byPermission) ? devices.byPermission : {};
157
183
  const entries = Array.isArray(view.permissions) ? view.permissions.filter(isObject) : [];
158
- const warnings = [...permissionStoreBuildWarnings(entries, String(view.environment)), ...permissionLaunchShellWarnings(entries, view.shell), ...trackingDeclarationWarnings(entries)];
184
+ const warnings = [...permissionStoreBuildWarnings(entries, String(view.environment)), ...permissionLaunchShellWarnings(entries, view.shell), ...permissionLockShellWarnings(entries, view.shell), ...trackingDeclarationWarnings(entries)];
159
185
  return {
160
186
  permissions: {
161
187
  catalogVersion: view.catalogVersion,
@@ -173,6 +199,10 @@ async function readDetail(platform, appId, section, scope) {
173
199
  ...(devices instanceof PlatformApiError ? { permissionDevicesError: devices.message } : {})
174
200
  };
175
201
  }
202
+ if (section === 'status')
203
+ return { status: unwrap(await platform.org('GET', `/mobile-store/${appId}/store-status`)) };
204
+ if (section === 'changes')
205
+ return { changes: unwrap(await platform.org('GET', `/mobile-store/${appId}/changes-since-production`)) };
176
206
  if (section === 'signing') {
177
207
  const [credentials, accounts] = await Promise.all([
178
208
  platform.org('GET', `/mobile-store/${appId}/credentials`),
@@ -310,7 +340,11 @@ function buildSummary(job, now) {
310
340
  elapsedSeconds: seconds(job.startedAt ?? job.createdAt, end),
311
341
  version: job.version,
312
342
  buildNumber: job.buildNumber,
343
+ environment: job.environment,
313
344
  runnerScope: job.runnerScope,
345
+ runnerKind: job.runnerKind,
346
+ packageType: job.packageType,
347
+ releaseTrack: job.releaseTrack,
314
348
  cancelRequested: job.cancelRequested,
315
349
  awaitingStore: job.status === 'running' && job.stage === 'store_processing',
316
350
  error: job.error,
@@ -326,11 +360,11 @@ export function registerMobileStoreTools(server, platform) {
326
360
  const config = platform.config;
327
361
  server.registerTool('mobile_store_get', {
328
362
  title: 'Mobil mağaza ayarını oku',
329
- description: "Mobil uygulamanın gizli olmayan mağaza ayarını döner (GET mobile-store/{appId}): kimlik (bundleIdentifier, androidPackage, scheme), sürüm ve sayaçlar, görünüm, izin metinleri, kipleri ve açılışta sorulanlar (permissions, permissionModes, launchPermissions), listing (dil başına mağaza metni), urls, apple, play, review; lockedFields (ilk derlemeden sonra değişmeyen alanlar), görsel yuvası başına sayı (assets: 'screenshot/tr/iphone69': 3) ve beyanlar (declarations: anahtar → işaretli mi); settings.ads reklam ayarıdır (kök ADR 0012: enabled, App ID'ler, testMode, consentForm, appOpenUnit, units[] = adBanner.unit ve ad-show.value'nun andığı yerleşimler) ve reklam açıkken adsWarnings döner (MOB-026: test kipi, platformda birimsiz yerleşim, konsol beyanları). Kayıt hiç yazılmamışsa persisted:false ve VARSAYILANLAR döner (kaydedilmez). manifest:true derlemeye gidecek manifesti de ekler (manifest-preview; sayaçlar SIRADAKİ numara); üretilemiyorsa manifestError alan adıyla döner. detail ayrıntı bölümleri ekler: 'assets' görselleri id'li liste olarak döner (mobile_asset_update / mobile_asset_delete id ister), 'signing' uygulamanın kimlik bilgilerini (signing.app: iOS profili, Android keystore, harita anahtarı, demo hesabı) ve organizasyonun Apple/Google hesaplarını + sertifikalarını (signing.accounts), 'runners' çevrimiçi runner'ları, 'audit' son 20 gizli bilgi olayını (subject: hangi tür, kim, ne zaman, hangi iş), 'permissions' izin görünümünü (permissions.permissions[]: uygulamanın isteyebileceği her izin için mode, text/defaultText, usage — izni kullanan sayfa·bileşen ya da akış adımı (setter ile yazılan tetikleyici prop dahil), ilk 20 yer + usageCount, çağrının environment'ından; usageReadable false ise o ortam okunamamıştır ve boş usage 'kullanılmıyor' demek değildir —, build — mağaza derlemesi taşıyor mu ve neden; HER ZAMAN production'dan: build.usageCount production'daki kullanım, build.staticUsageCount onun yazılı (ifadesiz) kısmı —, devices — aktif cihazların izin durumu sayıları; missingFromStoreBuilds — o izni taşımayan son mağaza derlemeleri; shell.carriesPermissions false ise kipler ve kabuğun henüz taşımadığı izinler bir sonraki kabuk sürümüyle etkili olur; shell.carriesLaunchPermissions false ise açılışta sorulan izinler (askOnLaunch) o kabukta sorulmaz — permissionWarnings söyler). Gizli DEĞER hiçbir bölümde yoktur — yalnız tür, maskeli id ve meta (ekip, son kullanma); bir bölüm okunamazsa (uç hatası) <bölüm>Error döner, çağrı düşmez. Gizli bilgiyi EKLEMEK/DEĞİŞTİRMEK MCP'den yapılmaz — Studio › Yayın › Hesaplar.",
363
+ description: "Mobil uygulamanın gizli olmayan mağaza ayarını döner (GET mobile-store/{appId}): kimlik (bundleIdentifier, androidPackage, scheme), sürüm ve sayaçlar, görünüm, izin metinleri, kipleri ve açılışta sorulanlar (permissions, permissionModes, launchPermissions), listing (dil başına mağaza metni), urls, apple, play, review; lockedFields (ilk derlemeden sonra değişmeyen alanlar), görsel yuvası başına sayı (assets: 'screenshot/tr/iphone69': 3) ve beyanlar (declarations: anahtar → işaretli mi); settings.ads reklam ayarıdır (kök ADR 0012: enabled, App ID'ler, testMode, consentForm, appOpenUnit, units[] = adBanner.unit ve ad-show.value'nun andığı yerleşimler) ve reklam açıkken adsWarnings döner (MOB-026: test kipi, platformda birimsiz yerleşim, konsol beyanları); settings.appLock uygulama kilididir (kök ADR 0021: enabled, timeoutSeconds 0·60·300·900). Kayıt hiç yazılmamışsa persisted:false ve VARSAYILANLAR döner (kaydedilmez). manifest:true derlemeye gidecek manifesti de ekler (manifest-preview; sayaçlar SIRADAKİ numara); üretilemiyorsa manifestError alan adıyla döner. detail ayrıntı bölümleri ekler: 'assets' görselleri id'li liste olarak döner (mobile_asset_update / mobile_asset_delete id ister), 'signing' uygulamanın kimlik bilgilerini (signing.app: iOS profili, Android keystore, harita anahtarı, demo hesabı) ve organizasyonun Apple/Google hesaplarını + sertifikalarını (signing.accounts), 'runners' çevrimiçi runner'ları, 'audit' son 20 gizli bilgi olayını (subject: hangi tür, kim, ne zaman, hangi iş), 'permissions' izin görünümünü (permissions.permissions[]: uygulamanın isteyebileceği her izin için mode, text/defaultText, usage — izni kullanan sayfa·bileşen ya da akış adımı (setter ile yazılan tetikleyici prop dahil), ilk 20 yer + usageCount, çağrının environment'ından; usageReadable false ise o ortam okunamamıştır ve boş usage 'kullanılmıyor' demek değildir —, build — mağaza derlemesi taşıyor mu ve neden; HER ZAMAN production'dan: build.usageCount production'daki kullanım, build.staticUsageCount onun yazılı (ifadesiz) kısmı —, devices — aktif cihazların izin durumu sayıları; missingFromStoreBuilds — o izni taşımayan son mağaza derlemeleri; shell.carriesPermissions false ise kipler ve kabuğun henüz taşımadığı izinler bir sonraki kabuk sürümüyle etkili olur; shell.carriesLaunchPermissions false ise açılışta sorulan izinler (askOnLaunch) o kabukta sorulmaz, shell.carriesAppLock false ise açık uygulama kilidi o kabukta yoktur — permissionWarnings söyler), 'status' mağaza durumunu (GET store-status: apple { state, appStoreState, versionString, releaseType, phased* }, play.tracks[] { track, status, versionCodes, rolloutPercent }, actions[] { platform, target, track, irreversible, consequence } — Studio › Yayınla'daki yayın düğmeleri; irreversible:true geri alınamaz, eylemi MCP başlatmaz, kullanıcıya consequence'ı söyleyip Studio'ya yönlendirin), 'changes' son production dağıtımından beri değişenleri iki kovada (GET changes-since-production: live — dağıtımla telefonlara gider, mağaza gerekmez; store — izin/kimlik/görsel gibi yeni mağaza sürümü isteyenler). Gizli DEĞER hiçbir bölümde yoktur — yalnız tür, maskeli id ve meta (ekip, son kullanma); bir bölüm okunamazsa (uç hatası) <bölüm>Error döner, çağrı düşmez. Gizli bilgiyi EKLEMEK/DEĞİŞTİRMEK MCP'den yapılmaz — Studio › Yayın › Hesaplar.",
330
364
  inputSchema: {
331
365
  applicationId,
332
366
  manifest: z.boolean().optional().describe('true: derleme manifestinin önizlemesini de ekler'),
333
- detail: z.array(z.enum(DETAIL_SECTIONS)).optional().describe("Ayrıntı bölümleri: 'assets' (id'li görsel listesi), 'signing' (kimlik bilgisi ve hesap durumu, değersiz), 'runners', 'audit', 'permissions' (izin görünümü; kullanılan bir izin mağazadaki son derlemede yoksa ya da açılışta sorulan izin varken yayındaki kabuk açılışta sormuyorsa permissionWarnings)"),
367
+ detail: z.array(z.enum(DETAIL_SECTIONS)).optional().describe("Ayrıntı bölümleri: 'assets' (id'li görsel listesi), 'signing' (kimlik bilgisi ve hesap durumu, değersiz), 'runners', 'audit', 'permissions' (izin görünümü; kullanılan bir izin mağazadaki son derlemede yoksa ya da açılışta sorulan izin varken yayındaki kabuk açılışta sormuyorsa permissionWarnings), 'status' (mağaza durumu + yayın eylemleri), 'changes' (canlıya al kovaları: live | store)"),
334
368
  project: projectParam.describe("Yalnız detail: ['permissions'] için: cihaz sayılarının okunacağı proje; boşsa DEFAULT_PROJECT"),
335
369
  environment: environmentParam.describe("Yalnız detail: ['permissions'] için: KULLANIMIN okunacağı ortam (boşsa DEFAULT_ENVIRONMENT). build her zaman production'dan hesaplanır; yalnız okumadır")
336
370
  },
@@ -405,28 +439,56 @@ export function registerMobileStoreTools(server, platform) {
405
439
  }));
406
440
  server.registerTool('mobile_build_start', {
407
441
  title: 'Mobil derleme başlat',
408
- description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner. dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight yalnız ios, internal yalnız android. Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Derlenen uygulama production ortamını okur — yükleme hedeflerinde açılış sayfası production'a dağıtılmış olmalı (P-06 fail); yalın build'de P-06 yalnız uyarıdır.",
442
+ description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar; iOS yükleme hedeflerinde App Review satırları P-15…P-21, yayın hedeflerinde kaynak derleme P-22); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner ('manual' satırlar başlatmayı engellemez, kullanıcıya söylenir). dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight ve app-store-review/app-store-release yalnız ios, internal ve play-promote yalnız android. Mağaza yayın hedefleri (app-store-review, app-store-release, play-promote + releaseTrack closed|production) geri dönüşsüz ya da insan onaylıdır: YALNIZ dryRun ile kabul edilir, gerçek gönderim Studio › Yayınla'dadır; mağaza durumu ve sıradaki eylem mobile_store_get { detail: ['status'] }. packageType: aab (varsayılan) | apk — APK yalnız android build'de (mağazaya gitmez, telefona kurulur). Runner: runnerScope verilmezse backend seçer — kurumun kendi Mac'i çevrimiçiyse dedicated, değilse Zeyrek'in ortak runner'ı (shared): kurumun kendi Mac'i yoksa iş Zeyrek'in runner'ına düşer; yanıtta runnerKind own | zeyrek. runnerId işi kullanıcının o Mac'ine ayırır (runnerScope gerekmez — iş dedicated kapsamında o runner'ı bekler; kullanıcının birden çok çevrimiçi Mac'i varken işin hangisine düşeceğini sabitler): runner kurumun kendi runner'ı değilse, platformu desteklemiyorsa ya da isteyene derleyemiyorsa 404 döner, runnerScope: 'shared' ile birlikte 400 döner — ikisinde de numara ayrılmaz; çevrimdışı Mac reddedilmez, iş onu bekler. runnerId verilmezse iş eskisi gibi ayrılmaz (kapsamdaki ilk yoklayan runner alır). Bu araç Cihazda dene önizlemesini başlatmaz (önizleme görevi, kod ve davet kullanıcının Studio'daki işidir). Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Ortam: environment (ad ya da slug; boş = production) derlemenin içeriğini o ortamdan alır — development, production ya da hazır bir dal; yalnız build | testflight | internal'da verilir, yükseltme hedefinde gateway reddeder: mağazaya yalnız production işinin derlemesi gider, development/dal test derlemesi asla yükseltilemez. Ön kontrolün kapsamı scope: 'store' (varsayılan; mağazaya giden işin bütün kuralları) | 'test' (Yayınla 3 · Test sürümü: yalnız TestFlight / Play iç testin istedikleri — kimlik, hesap ve imza, konsolda uygulama kaydı, seçilen ortamda açılış sayfası, runner; metin, görsel, beyan ve App Review satırları sorulmaz); environment verilince scope 'test' olur, 'store' ile birlikte reddedilir; scope işe yazılmaz. Derlenen uygulama işin ortamını okur — yükleme hedeflerinde açılış sayfası o ortamda olmalı (P-06 fail; production'da dağıtılmış olmalı); yalın build'de P-06 yalnız uyarıdır. Yanıt işin environment'ını taşır.",
409
443
  inputSchema: {
410
444
  applicationId,
411
445
  platform: mobilePlatform,
412
446
  target: buildTarget.optional(),
413
447
  runnerScope: z.enum(['dedicated', 'shared']).optional()
414
448
  .describe("dedicated: organizasyonun kendi runner'ı · shared: platformun ortak runner'ı. Verilmezse backend seçer (çevrimiçi dedicated varsa o, yoksa shared). Ön kontrol P-09 kapsamdan bağımsızdır; verilen kapsamda çevrimiçi runner yoksa iş hiç alınmaz ve numara yanardı — gateway bunu POST'tan önce durdurur. Durum: mobile_store_get { detail: ['runners'] }"),
449
+ runnerId: guid.optional().describe("İşi bu runner'a ayırır — kurumun kendi Mac'i (mobile_store_get { detail: ['runners'] } → runners.dedicated[].id). Verilmezse iş ayrılmaz"),
450
+ packageType: z.enum(['aab', 'apk']).optional().describe("Android paketi: aab (varsayılan, mağaza paketi) · apk (yalnız android build — telefona doğrudan kurulur, mağazaya gitmez; indirme bağlantısı Studio'da)"),
451
+ releaseTrack: z.enum(PLAY_TRACKS).optional().describe("Yalnız play-promote: closed (kapalı test) | production (herkese; play.rolloutPercent kadar). play-promote zaten yalnız dryRun ile kabul edilir"),
452
+ environment: z.string().trim().min(1).optional()
453
+ .describe("Derlemenin içeriğinin alındığı ortam (ad ya da slug: 'development', 'production', hazır dal). Boş = production. Yalnız build | testflight | internal; verilince ön kontrol scope 'test' ile o ortamda sorar. Yalnız production derlemesi sonradan mağazaya yükseltilebilir"),
454
+ scope: z.enum(PREFLIGHT_SCOPES).optional()
455
+ .describe("Ön kontrol kapsamı: store (varsayılan, mağazaya giden işin bütün kuralları) | test (yalnız TestFlight / Play iç test yüklemesinin istedikleri; yükseltme hedefinde verilmez). İşe yazılmaz"),
415
456
  dryRun: z.boolean().optional().describe('true: yalnız ön kontrol; iş açılmaz, numara ayrılmaz')
416
457
  },
417
458
  annotations: writes
418
- }, handle(config, async ({ applicationId: appId, platform: mobile, target, runnerScope, dryRun }) => {
459
+ }, handle(config, async ({ applicationId: appId, platform: mobile, target, runnerScope, runnerId, packageType, releaseTrack, environment, scope, dryRun }) => {
419
460
  const resolvedTarget = target ?? 'build';
420
- const allowed = { ios: ['build', 'testflight'], android: ['build', 'internal'] }[mobile];
461
+ const allowed = PLATFORM_TARGETS[mobile];
462
+ const promotion = RELEASE_TARGETS.includes(resolvedTarget);
421
463
  if (!allowed.includes(resolvedTarget)) {
422
- throw new PlatformApiError(`mobile_build_start: '${resolvedTarget}' hedefi ${mobile} için yok — ${mobile} yalnız ${allowed.join(' | ')} alır (testflight iOS'un, internal Android'in yükleme hedefidir). Hiçbir iş açılmadı.`, 400);
464
+ throw new PlatformApiError(`mobile_build_start: '${resolvedTarget}' hedefi ${mobile} için yok — ${mobile} yalnız ${allowed.join(' | ')} alır (testflight ve app-store-* iOS'un, internal ve play-promote Android'in hedefidir). Hiçbir iş açılmadı.`, 400);
465
+ }
466
+ if (releaseTrack !== undefined && resolvedTarget !== 'play-promote') {
467
+ throw new PlatformApiError(`mobile_build_start: releaseTrack yalnız 'play-promote' hedefinde verilir ('${resolvedTarget}' kanal seçmez). Hiçbir iş açılmadı.`, 400);
468
+ }
469
+ if (packageType === 'apk' && !(mobile === 'android' && resolvedTarget === 'build')) {
470
+ throw new PlatformApiError(`mobile_build_start: packageType 'apk' yalnız android build'de — APK mağazaya gitmez (Play AAB ister, iOS paketi IPA'dır). Mağaza hedefi için packageType'ı bırakın ya da 'aab' verin. Hiçbir iş açılmadı.`, 400);
471
+ }
472
+ if (environment !== undefined && promotion) {
473
+ throw new PlatformApiError(`mobile_build_start: environment yalnız build | testflight | internal hedefinde verilir — '${resolvedTarget}' yeni derleme yapmaz, production işinin derlemesini mağazaya taşır; development ya da dal test derlemesi yükseltilemez. environment'ı bırakın. Hiçbir iş açılmadı.`, 400, 'environment_on_promotion');
474
+ }
475
+ if (scope === 'test' && promotion) {
476
+ throw new PlatformApiError(`mobile_build_start: scope 'test' yalnız build | testflight | internal hedefinde verilir — '${resolvedTarget}' mağazaya giden iştir ve bütün mağaza kurallarını (scope 'store') sorar. scope'u bırakın. Hiçbir iş açılmadı.`, 400, 'scope_on_promotion');
477
+ }
478
+ if (environment !== undefined && scope === 'store') {
479
+ throw new PlatformApiError(`mobile_build_start: environment yalnız scope 'test' ile verilir — 'store' ön kontrolü mağazaya giden işi production'da sorar. scope'u bırakın (environment verilince 'test' olur) ya da environment'ı kaldırın. Hiçbir iş açılmadı.`, 400, 'environment_outside_test');
480
+ }
481
+ const resolvedScope = scope ?? (environment !== undefined ? 'test' : undefined);
482
+ // Geri dönüşsüz ya da insan onaylı gönderim (b-24): ajan yalnız ön kontrolü okur, POST hiç gitmez.
483
+ if (promotion && !dryRun) {
484
+ throw new PlatformApiError(`mobile_build_start: '${resolvedTarget}' MCP'den başlatılmaz — mağazaya gönderim geri dönüşsüz ya da insan onaylıdır (inceleme, herkese yayın, kanala taşıma). Kullanıcı Studio › Yayınla'da sonucu görüp onaylar. Ön kontrol: aynı çağrı dryRun: true ile; mağaza durumu ve eylemin sonucu: mobile_store_get { detail: ['status'] }. Hiçbir iş açılmadı.`, 400, 'release_target_studio_only');
423
485
  }
424
- const preflight = unwrap(await platform.org('GET', `/mobile-store/${appId}/preflight`, { query: { platform: mobile, target: resolvedTarget } }));
486
+ const preflight = unwrap(await platform.org('GET', `/mobile-store/${appId}/preflight`, { query: { platform: mobile, target: resolvedTarget, ...(resolvedScope ? { scope: resolvedScope } : {}), ...(environment ? { environment } : {}) } }));
425
487
  const checks = Array.isArray(preflight.checks) ? preflight.checks.filter(isObject) : [];
426
488
  const open = checks.filter(check => check.status !== 'ok');
427
489
  const scopeProblem = runnerScope ? await runnerScopeProblem(platform, mobile, runnerScope) : undefined;
428
490
  if (dryRun) {
429
- return { dryRun: true, platform: mobile, target: resolvedTarget, ready: preflight.ready, blocked: preflight.blocked, checks: open, okCount: checks.length - open.length, ...(scopeProblem ? { runnerScopeProblem: scopeProblem } : {}) };
491
+ return { dryRun: true, platform: mobile, target: resolvedTarget, environment: environment ?? 'production', scope: resolvedScope ?? 'store', ...(releaseTrack ? { releaseTrack } : {}), ready: preflight.ready, blocked: preflight.blocked, checks: open, okCount: checks.length - open.length, ...(scopeProblem ? { runnerScopeProblem: scopeProblem } : {}) };
430
492
  }
431
493
  if (scopeProblem)
432
494
  throw new PlatformApiError(`mobile_build_start: ${scopeProblem} İş BAŞLATILMADI, derleme numarası ayrılmadı.`, 422, 'runner_scope_offline');
@@ -438,7 +500,7 @@ export function registerMobileStoreTools(server, platform) {
438
500
  "Ayar satırları ve beyanlar mobile_store_update ile, görseller mobile_asset_create ile, Apple bundle ID'si (P-04) mobile_store_update { registerAppleBundleId: true } ile; gizli bilgi satırları Studio › Yayın › Hesaplar'da düzeltilir. Tam liste: mobile_build_start { dryRun: true }."
439
501
  ].join('\n'), 422, 'preflight_failed');
440
502
  }
441
- const job = unwrap(await platform.org('POST', '/mobile-builds', { body: { applicationId: appId, platform: mobile, target: resolvedTarget, ...(runnerScope ? { runnerScope } : {}) } }));
503
+ const job = unwrap(await platform.org('POST', '/mobile-builds', { body: { applicationId: appId, platform: mobile, target: resolvedTarget, ...(runnerScope ? { runnerScope } : {}), ...(runnerId ? { runnerId } : {}), ...(packageType ? { packageType } : {}), ...(releaseTrack ? { releaseTrack } : {}), ...(environment ? { environment } : {}) } }));
442
504
  const warnings = checks.filter(check => check.status === 'warn' || check.status === 'manual');
443
505
  return {
444
506
  ...buildSummary(job, new Date().toISOString()),
@@ -447,7 +509,7 @@ export function registerMobileStoreTools(server, platform) {
447
509
  }));
448
510
  server.registerTool('mobile_build_status', {
449
511
  title: 'Mobil derleme durumu',
450
- description: "buildId verilirse o işin durumunu döner (GET mobile-builds/{id}): status (queued | running | completed | failed | cancelled), stage (claimed → preparing → building → signing → uploading → store_processing), aşama başına süre (seconds), toplam süre, version + buildNumber, error { code, message, hint }, storeResult (testflight: testflightBuildId, processingState, listingError + listingMessage — derleme TestFlight'ta ama mağaza metni/görselleri reddedildi, iş yine tamamlanır; internal: versionCode, releaseStatus) ve awaitingStore (TestFlight işleme yoklaması — runner işten çıkmıştır, iş Apple VALID deyince completed olur, en çok 60 dk). Yalnız applicationId verilirse uygulamanın son işlerini özetle listeler (en yeni önce, ≤ 50). Hata kodunun önerisi platform_guide('mobile-publish', 'hata-kodlari') bölümündedir.",
512
+ description: "buildId verilirse o işin durumunu döner (GET mobile-builds/{id}): status (queued | running | completed | failed | cancelled), stage (claimed → preparing → building → signing → uploading → store_processing), aşama başına süre (seconds), toplam süre, version + buildNumber, environment (derlemenin içeriğinin alındığı ortam; yalnız 'production' işi mağazaya yükseltilebilir), error { code, message, hint }, storeResult (testflight: testflightBuildId, processingState, listingError + listingMessage — derleme TestFlight'ta ama mağaza metni/görselleri reddedildi, iş yine tamamlanır; internal: versionCode, releaseStatus) ve awaitingStore (TestFlight işleme yoklaması — runner işten çıkmıştır, iş Apple VALID deyince completed olur, en çok 60 dk). Yalnız applicationId verilirse uygulamanın son işlerini özetle listeler (en yeni önce, ≤ 50). Hata kodunun önerisi platform_guide('mobile-publish', 'hata-kodlari') bölümündedir.",
451
513
  inputSchema: {
452
514
  buildId: buildId.optional(),
453
515
  applicationId: applicationId.optional()
@@ -465,7 +527,7 @@ export function registerMobileStoreTools(server, platform) {
465
527
  return {
466
528
  applicationId: appId,
467
529
  count: jobs.length,
468
- builds: jobs.map(job => ({ id: job.id, platform: job.platform, target: job.target, status: job.status, stage: job.stage, buildNumber: job.buildNumber, version: job.version, errorCode: isObject(job.error) ? job.error.code : undefined, createdAt: job.createdAt, finishedAt: job.finishedAt })),
530
+ builds: jobs.map(job => ({ id: job.id, platform: job.platform, target: job.target, environment: job.environment, status: job.status, stage: job.stage, buildNumber: job.buildNumber, version: job.version, errorCode: isObject(job.error) ? job.error.code : undefined, createdAt: job.createdAt, finishedAt: job.finishedAt })),
469
531
  ...(jobs.length === 0 ? { note: 'Bu uygulamada hiç derleme işi yok — çağrı BAŞARILI, sonuç boş. Başlatmak için mobile_build_start.' } : {})
470
532
  };
471
533
  }));
@@ -500,7 +562,7 @@ export function registerMobileStoreTools(server, platform) {
500
562
  }, handle(config, async ({ buildId: id }) => buildSummary(unwrap(await platform.org('POST', `/mobile-builds/${id}/cancel`)), new Date().toISOString())));
501
563
  server.registerTool('mobile_asset_create', {
502
564
  title: 'Mağaza görseli yükle',
503
- description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'). Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
565
+ description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'); reviewAttachment (dilsiz, PNG/JPEG) App Review'a ek görseldir — yalnız app-store-review gönderimine girer. Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
504
566
  inputSchema: {
505
567
  applicationId,
506
568
  path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel PNG/JPEG yolu (page_screenshot outPath'i); content ile birlikte verilmez"),
@@ -35,6 +35,8 @@ import { heroTags } from '../knowledge/platform/heroTag.js';
35
35
  import { profileLintReadsSavedNodes } from '../knowledge/platform/lint.js';
36
36
  import { resolveThemeNative } from '../knowledge/styleResolveNative.js';
37
37
  import { boundFlowSteps, pageLayoutWarnings, screenProfileWarnings } from './profileWrite.js';
38
+ import { enforceTextHooks, textHookOverrideParam } from './textHookGuard.js';
39
+ import { overwrittenValues } from '../knowledge/textHooks.js';
38
40
  import { applyMobileSpacingDefaults, applyMobileSpacingToOps, emptyMobileSpacingFix, mobileSpacingNote } from '../knowledge/platform/mobileSpacing.js';
39
41
  import { shellLessPageSkeleton } from '../knowledge/platform/mobile.js';
40
42
  import { z } from 'zod';
@@ -563,15 +565,17 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
563
565
  ...(shot.finalUrl === undefined ? {} : { finalUrl: shot.finalUrl }),
564
566
  ...(shot.pageState === 'ready-no-flows'
565
567
  ? { pageStateNote: 'Runtime çizildi ama hiç mount akışı koşmadı. Sayfa akışsızsa (giriş ekranı) ya da verisi veri kaynağı bağlantısından geliyorsa (connectData prop akış koşturmadan okunur; summaryText satırları gösterir) normaldir; veri bekliyor ve göremiyorsanız page_schema_get ile mount olayına bağlı akışa, bağlı prop için connect_data_get / connect_data_execute (op list) ile bağlantıya bakın.' }
566
- : shot.pageState === 'ready-no-counter'
567
- ? { pageStateNote: 'Runtime koşan akış sayacı yayınlamıyor (eski runtime); hazırlık DOM durulmasından çıkarıldı, akışların bittiği kanıtlanamadı.' }
568
- : shot.pageState === 'error'
569
- ? { pageStateNote: 'Studio sayfa için hata/bulunamadı durumunu çizdi; kare o durumu gösterir. apiStatus ve slug\'ları doğrulayın.' }
570
- : shot.pageState === 'redirected'
571
- ? { pageStateNote: `İstenen adres runtime dışına yönlendi (finalUrl). Route guard ya da Studio giriş ekranı olabilir; kare yönlendirilen sayfayı gösterir.` }
572
- : shot.pageState === 'not-ready'
573
- ? { pageStateNote: 'TEŞHİS KARESİ (captureUnready): bütçe dolduğunda sayfa hazır değildi; kare iskelet ya da yarım durumu gösterebilir, doğrulama için kullanmayın.' }
574
- : {}),
568
+ : shot.pageState === 'awaiting-user'
569
+ ? { pageStateNote: "Bir akış kullanıcı penceresini bekliyor: önizlemenin biyometri penceresi açık (kök ADR 0021). Kare pencereyi gösterir; dalı sürmek için sonraki eylem { action: 'click', target: 'preview-biometric__verify' } (Doğrulandı say) ya da 'preview-biometric__cancel' (vazgeç). Production /view'da pencere yoktur." }
570
+ : shot.pageState === 'ready-no-counter'
571
+ ? { pageStateNote: 'Runtime koşan akış sayacı yayınlamıyor (eski runtime); hazırlık DOM durulmasından çıkarıldı, akışların bittiği kanıtlanamadı.' }
572
+ : shot.pageState === 'error'
573
+ ? { pageStateNote: 'Studio sayfa için hata/bulunamadı durumunu çizdi; kare o durumu gösterir. apiStatus ve slug\'ları doğrulayın.' }
574
+ : shot.pageState === 'redirected'
575
+ ? { pageStateNote: `İstenen adres runtime dışına yönlendi (finalUrl). Route guard ya da Studio giriş ekranı olabilir; kare yönlendirilen sayfayı gösterir.` }
576
+ : shot.pageState === 'not-ready'
577
+ ? { pageStateNote: 'TEŞHİS KARESİ (captureUnready): bütçe dolduğunda sayfa hazır değildi; kare iskelet ya da yarım durumu gösterebilir, doğrulama için kullanmayın.' }
578
+ : {}),
575
579
  ...(shot.renderWaitTimedOut && shot.pageState === 'not-ready'
576
580
  ? { renderWaitTimedOut: true, note: "Sayfanın veri isteği gözlenmedi — STUDIO_BASE_URL ve slug'ları doğrulayın." }
577
581
  : {}),
@@ -711,7 +715,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
711
715
  * kök eklemenin taşınmasını ve `resize_component` planını da koşmuyordu. Emsal: ARC-143, `page-meta.test.ts`
712
716
  * "validate = yazma". Regresyon: `test/page-dry-run-parity.test.ts`.
713
717
  */
714
- const planPageWrite = async (tool, { project, environment, applicationId, pageId, ops, operations, expectedUpdatedAt, allowFlowDeletion }) => {
718
+ const planPageWrite = async (tool, { project, environment, applicationId, pageId, ops, operations, expectedUpdatedAt, allowFlowDeletion, policyOverride }) => {
715
719
  const opened = openBatchEnvelopes(tool, microOps(tool, ops, operations).map(op => op));
716
720
  const allOps = opened.ops;
717
721
  const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
@@ -798,6 +802,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
798
802
  }
799
803
  if (rejected.length > 0)
800
804
  throw new PlatformApiError(rejectedOpsMessage(rejected, batchIndexHint(opened)), 400);
805
+ // TR-005 / TR-006: yamanın yükü op'ların EZDİĞİ kayıtlı değerle karşılaştırılır — orada zaten duran kanca eskidir.
806
+ // Kuru koşu aynı kararı verir (PAGE-049): yazımın durduracağı yamayı o da durdurur.
807
+ const textFindings = await enforceTextHooks(config, {
808
+ platform, project, environment, applicationId,
809
+ after: [...opRecords, ...propTypeOps],
810
+ before: async () => overwrittenValues((await readPage()).schema, [...opRecords, ...propTypeOps]),
811
+ hostTree: async () => (await readPage()).schema,
812
+ where: 'sayfa yaması'
813
+ }, policyOverride);
801
814
  stripGatewayEventFields(opRecords);
802
815
  const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
803
816
  let retargeted = [];
@@ -923,6 +936,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
923
936
  flowWarnings,
924
937
  flowsBeforePatch,
925
938
  layoutWarnings,
939
+ textFindings,
926
940
  readVersion: () => (typeof cachedPage?.version === 'string' ? cachedPage.version : undefined),
927
941
  notes: [
928
942
  ...batchEnvelopeNote(opened),
@@ -941,10 +955,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
941
955
  server.registerTool('page_schema_validate', {
942
956
  title: 'Sayfa şemasını doğrula (kuru çalıştırma)',
943
957
  description: "Micro-op'ları kaydetmeden şemaya uygular ve doğrulama hatalarını (suggestions ile) döndürür; page_schema_patch öncesi planı test etmek için. page_schema_patch'in PLANINI koşar (PAGE-049): aynı ön kontroller — yazımın reddedeceği yamayı aynı mesajla, istek göndermeden reddeder — aynı onarım ve platforma aynı gövde; yazımın döneceği uyarılar (layoutWarnings, formWarnings, flowWarnings) burada da görünür.",
944
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid, ops: opsParam.optional(), operations: opsParam.optional().describe("'ops' ile aynı alan (eşanlamlı) — entity/microservice/workflow patch'leriyle aynı adı kullanabilmeniz için"), allowFlowDeletion: z.boolean().optional().describe('page_schema_patch\'e vereceğiniz değer: true ise akışı referanssız bırakan yama kuru koşuda da reddedilmez. Varsayılan false — yazım onu reddeder, kuru koşu da (G-21).') },
958
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid, ops: opsParam.optional(), operations: opsParam.optional().describe("'ops' ile aynı alan (eşanlamlı) — entity/microservice/workflow patch'leriyle aynı adı kullanabilmeniz için"), allowFlowDeletion: z.boolean().optional().describe('page_schema_patch\'e vereceğiniz değer: true ise akışı referanssız bırakan yama kuru koşuda da reddedilmez. Varsayılan false — yazım onu reddeder, kuru koşu da (G-21).'), policyOverride: textHookOverrideParam },
945
959
  annotations: readOnly
946
- }, handle(config, async ({ project, environment, applicationId, pageId, ops, operations, allowFlowDeletion }) => {
947
- const plan = await planPageWrite('page_schema_validate', { project, environment, applicationId, pageId, ops, operations, allowFlowDeletion });
960
+ }, handle(config, async ({ project, environment, applicationId, pageId, ops, operations, allowFlowDeletion, policyOverride }) => {
961
+ const plan = await planPageWrite('page_schema_validate', { project, environment, applicationId, pageId, ops, operations, allowFlowDeletion, policyOverride });
948
962
  const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
949
963
  environment,
950
964
  body: { ops: plan.serverOps }
@@ -961,7 +975,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
961
975
  if (plan.layoutWarnings.length > 0)
962
976
  response.layoutWarnings = plan.layoutWarnings;
963
977
  Object.assign(response, applicationTypeField(plan.resolved));
964
- return withNormalization(response, plan.notes);
978
+ return attachFindings(withNormalization(response, plan.notes), plan.textFindings);
965
979
  }));
966
980
  server.registerTool('page_schema_patch', {
967
981
  title: 'Sayfa şemasına patch uygula',
@@ -976,11 +990,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
976
990
  baseVersion: z.string().optional().describe("page_schema_get çıktısındaki version ya da önceki page_schema_patch yanıtındaki newVersion; sayfa o sürümden beri değiştiyse 409 döner ve hiçbir op uygulanmaz"),
977
991
  expectedUpdatedAt: z.string().optional().describe("Eski koruma: page_schema_get çıktısındaki updatedAt; eşleşmezse patch uygulanmaz. baseVersion tercih edilir"),
978
992
  allowFlowDeletion: z.boolean().optional().describe("true: bu yama bir akışı referanssız bırakacaksa yine de uygulanır. Varsayılan false — platform referanssız akışı KALICI siler ve gateway isteği önden reddeder (G-21). Silinen akışlar yanıtta deletedFlows ile listelenir."),
979
- view: writeViewParam
993
+ view: writeViewParam,
994
+ policyOverride: textHookOverrideParam
980
995
  },
981
996
  annotations: updates
982
- }, handle(config, async ({ project, environment, applicationId, pageId, ops, operations, baseVersion, expectedUpdatedAt, allowFlowDeletion, view }) => {
983
- const plan = await planPageWrite('page_schema_patch', { project, environment, applicationId, pageId, ops, operations, expectedUpdatedAt, allowFlowDeletion });
997
+ }, handle(config, async ({ project, environment, applicationId, pageId, ops, operations, baseVersion, expectedUpdatedAt, allowFlowDeletion, view, policyOverride }) => {
998
+ const plan = await planPageWrite('page_schema_patch', { project, environment, applicationId, pageId, ops, operations, expectedUpdatedAt, allowFlowDeletion, policyOverride });
984
999
  const { opRecords, sentOps, serverOps, resolved, removedEvents, resized, propTypes, deletedFlows, formWarnings, flowWarnings, flowsBeforePatch, layoutWarnings } = plan;
985
1000
  // Tek istek, tek sürüm: öykünülen op'lar 2026-09-14'e kadar ayrı GET + tam PUT çiftleriydi ve
986
1001
  // yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
@@ -1058,7 +1073,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1058
1073
  }
1059
1074
  }
1060
1075
  Object.assign(response, applicationTypeField(resolved));
1061
- return withWriteDiff(withNormalization(response, plan.notes), opDrops);
1076
+ return attachFindings(withWriteDiff(withNormalization(response, plan.notes), opDrops), plan.textFindings);
1062
1077
  }));
1063
1078
  server.registerTool('page_discover', {
1064
1079
  title: 'Tasarım ekranından sayfa iskeleti öner',
@@ -1093,10 +1108,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1093
1108
  events: eventsParam.optional(),
1094
1109
  schema: schemaParam.optional(),
1095
1110
  view: writeViewParam,
1111
+ policyOverride: textHookOverrideParam,
1096
1112
  ifExists: ifExistsParam('sayfa', "mevcut sayfayı page_update gibi günceller — yalnız bu çağrıda VERİLEN alanlar yazılır, verilmeyen şema/olaylar/roller korunur (page_schema_patch ile kurulmuş şema betik tekrar koşunca silinmez); id'si ve flow'ları korunur. Şema verilirse sayfanın şeması BİLEREK tam yazılır (page_update force:true gibi — gateway sayfayı okuduğu anın sürümüyle yazar, aradaki Studio değişikliği ezilir); hedefli değişiklik için page_schema_patch")
1097
1113
  },
1098
1114
  annotations: writes
1099
- }, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, isAnonymous, isLoginPage, events, schema, view, ifExists }) => {
1115
+ }, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, isAnonymous, isLoginPage, events, schema, view, ifExists, policyOverride }) => {
1100
1116
  const resolved = await resolveProfile(platform, applicationId);
1101
1117
  const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
1102
1118
  const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
@@ -1134,6 +1150,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1134
1150
  copyCheck: "kullanılıp kullanılmadığını dependency_graph module:'page' ile kontrol edip gerekirse silin",
1135
1151
  updateTool: 'pageId ile page_update'
1136
1152
  });
1153
+ // TR-005 / TR-006: ifExists:'update' mevcut sayfayı yeniden yazar — onda zaten duran kanca eskidir.
1154
+ const textFindings = schema === undefined
1155
+ ? []
1156
+ : await enforceTextHooks(config, {
1157
+ platform, project, environment, applicationId,
1158
+ after: container.schema,
1159
+ before: async () => existing === undefined ? undefined : (await fetchHost(platform, 'page', project, environment, applicationId, String(existing.id))).schema,
1160
+ where: `sayfa '${name}'`
1161
+ }, policyOverride);
1137
1162
  if (existing !== undefined) {
1138
1163
  const pageId = String(existing.id);
1139
1164
  const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
@@ -1163,7 +1188,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1163
1188
  };
1164
1189
  if (updateWarnings.length > 0)
1165
1190
  summary.layoutWarnings = updateWarnings;
1166
- return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...skeletonNotes, ...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved), ...mobileSpacingNote(spacing)]);
1191
+ return attachFindings(withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...skeletonNotes, ...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved), ...mobileSpacingNote(spacing)]), textFindings);
1167
1192
  }
1168
1193
  // PAGE-051 yalnız gerçekten OLUŞTURAN dalda: `ifExists: 'update'` var olan sayfada page_update gibi
1169
1194
  // davranır ve kayıtlı emekli düğüm yazılabilir kalmalıdır.
@@ -1204,7 +1229,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1204
1229
  const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(container.schema)) : []), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'), ...profileWarnings,
1205
1230
  ...await pageLayoutWarnings(platform, resolved, project, environment, applicationId, { tree: container.schema, layoutId })];
1206
1231
  const typeField = applicationTypeField(resolved);
1207
- const findings = await legacyRoleListFindings(platform, project, environment, { roleIds }, 'page');
1232
+ const findings = [...await legacyRoleListFindings(platform, project, environment, { roleIds }, 'page'), ...textFindings];
1208
1233
  if (!normalized || typeof normalized !== 'object')
1209
1234
  return attachFindings(normalized, findings);
1210
1235
  if (createWarnings.length === 0)
@@ -1232,10 +1257,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1232
1257
  schema: schemaParam.optional().describe('Nadiren gerekir; verilirse her düğümde elementId, name (= elementId), type, blocks zorunlu (PAGE-037, gateway blocks doldurmaz); verilmezse mevcut şema korunur. Tam REPLACE: kayıtlı sayfadan daha az düğüm taşıyan şema allowNodeLoss olmadan reddedilir (PAGE-043)'),
1233
1258
  allowNodeLoss: z.boolean().optional().describe('true: gönderilen schema kayıtlı sayfadan daha az düğüm taşısa da yazılır — eksik düğümler geri dönüşsüz silinir. Varsayılan false: PAGE-043 ile reddedilir (kırpık okumanın geri yazılması)'),
1234
1259
  baseVersion: z.string().optional().describe("Değişikliği planladığınız sayfa sürümü (page_get / page_schema_get yanıtındaki version). Verilmezse bu gateway'in o sayfada en son gördüğü sürüm gider; sayfa o sürümden beri değiştiyse 409 döner ve hiçbir şey yazılmaz"),
1235
- force: z.boolean().optional().describe("true: sürüm kontrolünü bilerek geçer — gateway sayfanın son sürümünü okuyup onun üstüne yazar, aradaki değişiklik (Studio sekmesi, başka ajan) ezilir. Yanıt forced: true taşır. Varsayılan false")
1260
+ force: z.boolean().optional().describe("true: sürüm kontrolünü bilerek geçer — gateway sayfanın son sürümünü okuyup onun üstüne yazar, aradaki değişiklik (Studio sekmesi, başka ajan) ezilir. Yanıt forced: true taşır. Varsayılan false"),
1261
+ policyOverride: textHookOverrideParam
1236
1262
  },
1237
1263
  annotations: updates
1238
- }, handle(config, async ({ project, environment, applicationId, pageId, allowNodeLoss, baseVersion, force, ...changes }) => {
1264
+ }, handle(config, async ({ project, environment, applicationId, pageId, allowNodeLoss, baseVersion, force, policyOverride, ...changes }) => {
1239
1265
  const knownVersion = baseVersion ?? schemaReadMemory.lastVersion(pageKey(project, environment, pageId));
1240
1266
  if (knownVersion === undefined && force !== true) {
1241
1267
  throw new PlatformApiError("PAGE-039 — page_update hangi sürümün üstüne yazdığını bilmiyor: baseVersion verilmedi ve bu gateway sayfayı henüz okumadı. Platformun PUT'u REPLACE'tir; sürümsüz tam gövde Studio'da açık bir sekmenin ya da başka bir ajanın değişikliğini sessizce ezer (Bitki Bakım koşusu, 14:02). Önce page_get ya da page_schema_get ile okuyun — yanıttaki version gateway'de hatırlanır — ya da onu baseVersion olarak verin. Son sürümün üstüne bilerek yazmak için force:true. İstek gönderilmedi.", 400);
@@ -1260,6 +1286,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1260
1286
  throw new PlatformApiError(`PAGE-043 — gönderilen şema ${sent} düğüm, kayıtlı sayfa ${stored} düğüm; eksilen elementId'ler: ${listed}${more}. Platformun PUT'u REPLACE'tir: eksik düğümler geri dönüşsüz silinir — kırpık (allowTruncated) ya da yarım bir okuma geri yazılıyor olabilir, istek gönderilmedi. Şema değişikliğini page_schema_patch micro-op'larıyla yapın (silmek için remove_component); düğümleri bilerek siliyorsanız allowNodeLoss:true verin.`, 400);
1261
1287
  }
1262
1288
  }
1289
+ // TR-005 / TR-006: tam şema yazımı kayıtlı şemayla karşılaştırılır — orada zaten duran kanca eskidir.
1290
+ const textFindings = overrides.schema === undefined
1291
+ ? []
1292
+ : await enforceTextHooks(config, {
1293
+ platform, project, environment, applicationId,
1294
+ after: overrides.schema,
1295
+ before: async () => current.schema,
1296
+ where: `sayfa '${String(current.name ?? pageId)}'`
1297
+ }, policyOverride);
1263
1298
  const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
1264
1299
  const fixed = normalizeComponentDefaults(overrides.schema);
1265
1300
  const reset = overrides.schema === undefined || !blocksMeaningful ? [] : resetPageContainerPosition(overrides.schema);
@@ -1296,7 +1331,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
1296
1331
  summary.forced = true;
1297
1332
  if (gaps.length > 0 || skeletonWarnings.length > 0)
1298
1333
  summary.layoutWarnings = [...gaps, ...skeletonWarnings];
1299
- const findings = await legacyRoleListFindings(platform, project, environment, overrides, 'page');
1334
+ const findings = [...await legacyRoleListFindings(platform, project, environment, overrides, 'page'), ...textFindings];
1300
1335
  return attachFindings(withNormalization(summary, [...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), findings);
1301
1336
  }));
1302
1337
  server.registerTool('page_clone', {
@@ -67,8 +67,9 @@ const guideRouting = {
67
67
  page: 'sayfa/flow işleri; prop\'u veri kaynağına bağlama (connectData, Kural 1c-3); flex düzen prop grubu ve boyut modları (Kural 1f-2)',
68
68
  layout: 'yerleşim kabuğu: tek slot, views, dar ekranda yığılma, widthType fill/fixed/auto',
69
69
  mobile: "mobil UYGULAMA (type:'mobile'): kurulum sırası (mobile_app_create), flex-only yerleşim, tipli stil, sekmeler (properties.mobile.tabs) ve çekmece, sayfa mount/focus olayları, MOB-* bulguları, page_screenshot device",
70
- 'mobile-steps': "mobil uygulamada cihaza dokunan akış adımları (redirect, alert, confirm, download, storage, setcookie, share, haptic, location, notification-*, permission, open-settings, back, ad-show, scan-code): mobildeki davranış, args sözleşmesi, Steps.<adım> sonucu; bildirim, izin, reklam ve konum tarifleri",
71
- 'mobile-publish': "mobil uygulamanın mağaza yayını: mobile_store_* / mobile_build_* akışı, ön kontrol P-01…P-14 (push kimlikleri, izinler, reklam ve kod tarama dahil), derleme hata kodları, insanın Studio'da yaptıkları",
70
+ 'mobile-steps': "mobil uygulamada cihaza dokunan akış adımları (redirect, alert, confirm, download, storage, setcookie, share, haptic, location, notification-*, permission, open-settings, back, ad-show, scan-code, biometric-auth): mobildeki davranış, args sözleşmesi, Steps.<adım> sonucu; bildirim, izin, reklam, biyometri ve konum tarifleri; FLOW-029/030, MOB-024/026/027",
71
+ 'mobile-publish': "mobil uygulamanın mağaza yayını: altı adımlı yolculuk, mobile_store_* / mobile_build_* akışı, ön kontrol P-01…P-22 (push kimlikleri, izinler, reklam, kod tarama, App Review), yayın hedefleri (yalnız dryRun), iki tür güncelleme, derleme hata kodları, insanın Studio'da yaptıkları (test kullanıcıları dahil)",
72
+ 'mobile-connect-data': "mobil uygulamada veri bağlamanın telefon farkı: alt sayfa / ↗ tam ekran seçim (lookup.mobileMode, mobileRow), sonsuz kaydırma (behavior.mobile), datatable kartı (mobileCard), gruplu liste grouping, autocomplete araması, CD-004/019/020",
72
73
  'mobile-layout': 'mobil ekranın mizanpajı — tek dil: güvenli alan, tek kaydırıcı, 16 dp kenar, gap ritmi, saydam/yüzeyli grup, genişlik, yarıçap/yazı ölçeği; MOB-015…021 reçeteleri ve layoutReport hiza ölçümü',
73
74
  component: 'komponent modeli, olay sözleşmeleri, appearance boyut sözlüğü (widthString/heightString)',
74
75
  html: 'hazır komponent yetmediğinde tek seferlik kaçış kapısı (Mustache, fireEvent, script kuralları)',
@@ -80,6 +81,7 @@ const guideRouting = {
80
81
  translation: 'sayfa/içerik çevirileri (çoklu dil)',
81
82
  access: 'kompleks proje yetkisi: organizasyon ağacı, izin kataloğu, kapsam, grant/atama, alan koruması (maske · şifre · türetilmiş alan)',
82
83
  merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları",
84
+ deployment: "development → production deploy'u neyi taşır: yalnız tanım; tablo satırları recordTransferMode ile, dosyalar yalnız id referansıyla; production'da eksik satır/dosya teşhisi",
83
85
  bpm: 'süreç motoru: tanım modeli, düğümler, atama sözlüğü, bölge olayları, kutu ve görev eylemleri, bağlayıcı/belge/imza/kanallar (süreç ≠ workflow)',
84
86
  decision: 'DMN karar tabloları: model, isabet politikaları, FEEL alt kümesi, DRD, süreçte decision düğümü, DMN XML'
85
87
  };