@appaflytech/zeyrek-ai-mcp 0.0.51 → 0.0.53
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.
- package/dist/knowledge/componentCatalog.js +3 -1
- package/dist/knowledge/componentEvents.js +13 -1
- package/dist/knowledge/expressionPrelude.js +3 -0
- package/dist/knowledge/mobileComponentCatalog.js +4 -1
- package/dist/knowledge/mobileStore.js +258 -1
- package/dist/knowledge/normalize.js +4 -3
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +79 -0
- package/dist/knowledge/rules.js +199 -6
- package/dist/knowledge/storeImport.js +69 -0
- package/dist/platform/argumentAliases.js +13 -4
- package/dist/platform/bodyFile.js +195 -38
- package/dist/platform/capture/actions.js +24 -5
- package/dist/platform/capture/capture.js +80 -11
- package/dist/platform/capture/persistent-browser.js +23 -1
- package/dist/platform/capture/readiness.js +32 -1
- package/dist/platform/errors.js +45 -5
- package/dist/platform/http.js +17 -9
- package/dist/platform/responses.js +7 -1
- package/dist/platform/secrets.js +8 -0
- package/dist/platform/token.js +32 -21
- package/dist/resources/examples/flow-credit-spend.json +233 -0
- package/dist/resources/examples/flow-purchase-paywall.json +662 -0
- package/dist/resources/examples/mobile-offline-list.json +1 -1
- package/dist/resources/examples/mobile-offline-queue.json +1 -1
- package/dist/resources/examples/mobile-offline-seamless.json +12 -3
- package/dist/resources/examples/store-import-mapping.json +25 -0
- package/dist/resources/guides/deployment.md +9 -0
- package/dist/resources/guides/expressions.md +1 -0
- package/dist/resources/guides/microservice.md +13 -1
- package/dist/resources/guides/mobile-publish.md +67 -2
- package/dist/resources/guides/mobile-steps.md +91 -3
- package/dist/resources/guides/mobile.md +38 -8
- package/dist/resources/guides/page.md +39 -8
- package/dist/resources/guides/translation.md +3 -1
- package/dist/resources/guides/workflow.md +30 -0
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +6 -1
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +4 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +6 -1
- package/dist/resources/schemas/StepDto.json +6 -1
- package/dist/resources/schemas/UpdateWorkflowCommand.json +6 -1
- package/dist/server.js +4 -0
- package/dist/tools/applications.js +7 -2
- package/dist/tools/deployments.js +1 -1
- package/dist/tools/designSchemaPatch.js +7 -2
- package/dist/tools/environments.js +40 -0
- package/dist/tools/flows.js +102 -9
- package/dist/tools/mobile.js +56 -0
- package/dist/tools/mobilePurchases.js +236 -0
- package/dist/tools/mobileStore.js +2 -2
- package/dist/tools/mobileStoreImport.js +173 -0
- package/dist/tools/pages.js +45 -11
- package/dist/tools/platformInfo.js +4 -4
- package/dist/tools/screenWrite.js +107 -1
- package/dist/tools/variables.js +1 -1
- package/package.json +1 -1
- package/dist/resources/guides/mobile-flow.md +0 -64
|
@@ -374,6 +374,35 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
|
|
|
374
374
|
| `automation` | Entity satır olayına tepki | `entityId` + `operation` + `isQueue` | returnDtoId YASAK; aşağıdaki "Automation gerçekleri" |
|
|
375
375
|
| `middleware` | Microservice/sayfa **çalışmadan önce** koşan guard/setter (auth, `user`/`settings` değişkenleri) | Bağlandığı uç çağrılınca (`middlewareIds`) | returnDtoId gerekmez; `set-user`/`variable`/`setcookie`/`redirect` yalnız bu tipte. **Ayrı rehberi var: `platform_guide('middleware')`** |
|
|
376
376
|
|
|
377
|
+
## Balance adımı: bakiye oku, düş, ekle, hak var mı (kök ADR 0027, `StepType.Balance`)
|
|
378
|
+
|
|
379
|
+
Mobil uygulamanın tüketilen ürün bakiyesini (kredi, jeton) sunucu iş akışından yönetir. İşlem `rule.balanceOperation`
|
|
380
|
+
(`read` · `spend` · `grant` · `entitled`), girdi `rule.expression` nesne literalidir (parantezle ya da doğrudan `{ … }`):
|
|
381
|
+
`application` (mobil uygulama slug'ı) ya da `applicationId`, `userId` (yoksa oturumdaki uygulama kullanıcısı) ya da
|
|
382
|
+
`installationId`, `balance`, `amount` (tam sayı ya da metin), `reason` (`grant`, ≤ 200), `idempotencyKey`, `entitlement`
|
|
383
|
+
(`entitled`). Çıktı `read`/`spend`/`grant`'ta `{ balance, balances }`, `entitled`'da `{ entitled, expiresAt }` (birden çok
|
|
384
|
+
açık satırdan biri süresizse `expiresAt: null`). Hata — yetersiz bakiye, geçersiz girdi, kullanıcı yok — adımın **hata
|
|
385
|
+
dalına** gider; `BalanceInsufficientException` kodu `errorCode` olarak okunur. Üç workflow tipinde de serbesttir.
|
|
386
|
+
|
|
387
|
+
- **Bakiye adı katalogdadır:** statik `balance` uygulamanın tüketilen ürünlerinden birinin `balance` alanı, statik
|
|
388
|
+
`entitlement` bir ürünün `entitlement`'ı olmalı — olmayan ad yazımda `FLOW-046` ile durur (hata katalogdakileri sayar;
|
|
389
|
+
`mobile_product_list`). İfadeyle (`input.ad`) verilen ad yazımda denetlenmez. Katalog akışın uygulamasından (ya da
|
|
390
|
+
ifadedeki GUID `applicationId`'den) okunur; okunamazsa kural susar.
|
|
391
|
+
- **Tekrar güvenliği varsayılan DEĞİLDİR:** `idempotencyKey` verilmezse adım çalışma kimliği kullanılır ve o her çalışmada
|
|
392
|
+
yenidir — yeniden koşabilen akış (yeniden deneme, döngü, aynı olayın iki kez gelmesi) iki kez düşer/ekler. `spend`/`grant`
|
|
393
|
+
için olayı tanımlayan bir anahtar verin (`idempotencyKey: input.davetId`); `FLOW-046` anahtarsız `spend`/`grant`'ı
|
|
394
|
+
uyarır. `spend:` ve `grant:` anahtar uzayları ayrıdır.
|
|
395
|
+
- **Tarif — krediyi düş, işi yap, iş düşerse geri ekle:** (1) `balance` `spend` `{ balance: 'kredi', amount: 10,
|
|
396
|
+
idempotencyKey: input.istekId }`; `success` → (2) işi yapan adım (ör. `http`/`microfunction`); onun `fail` dalı → (3)
|
|
397
|
+
`balance` `grant` `{ balance: 'kredi', amount: 10, reason: 'rapor üretilemedi', idempotencyKey: 'iade-' + input.istekId }`;
|
|
398
|
+
`spend`'in kendi hata dalı (`insufficient`) → `response` 409 gövdesi. Önce düşmek yarışta bakiyeyi korur; iade ayrı
|
|
399
|
+
anahtarlı olduğu için tekrar koşarsa bir kez yazılır. Defterde `grant` satırı mağaza iadesinden (`refund`) ayrı türdür.
|
|
400
|
+
- **Kilit:** `spend`/`grant` aynı sahibin eşzamanlı harcamasını sıralayan danışma kilidini işlem sonuna kadar tutar —
|
|
401
|
+
`balance` adımını akışın sonuna yakın ve kısa tutun, ondan sonra uzun süren adım (HTTP, e-posta) koyma.
|
|
402
|
+
- `entitled` sunucudaki hak satırlarına bakar (açık ve süresi geçmemiş); istemci ne yollarsa yollasın cevap değişmez —
|
|
403
|
+
istemci tarafı `Entitlements.<hak>` yalnız ekranı gizler, sunucu denetimi bunun yerine geçmez.
|
|
404
|
+
- Önizleme (`workflow_preview`) işlemi geri alır: `grant`/`spend` gerçekten koşar ve kalıcı satır bırakmaz.
|
|
405
|
+
|
|
377
406
|
## Step tipi × workflow tipi izin matrisi
|
|
378
407
|
|
|
379
408
|
| Step | workflow | automation | middleware |
|
|
@@ -384,6 +413,7 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
|
|
|
384
413
|
| convert | ✓ | – | – |
|
|
385
414
|
| revert (Fail and Rollback) | – | ✓ | – |
|
|
386
415
|
| process (BPM süreci) | ✓ | ✓ | – |
|
|
416
|
+
| balance (uygulama bakiyesi / hak denetimi) | ✓ | ✓ | ✓ |
|
|
387
417
|
|
|
388
418
|
> **`process` adımı süreci başlatır ya da ona sinyal/vekalet yazar, BEKLEMEZ (2026-09-18).**
|
|
389
419
|
> Kural alanları `processSlug`, `processOperation: start | signal | delegate` (boş = `start`) ve
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
3
|
-
"generatedAt": "2026-
|
|
2
|
+
"hash": "162e8c2d14c5a0737d023b55790bab139812a00ec8e2e6335ed51981bb27e548",
|
|
3
|
+
"generatedAt": "2026-11-02T08:52:07.624Z",
|
|
4
4
|
"types": 377,
|
|
5
5
|
"files": 196,
|
|
6
6
|
"note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
|
|
@@ -233,7 +233,8 @@
|
|
|
233
233
|
"revert",
|
|
234
234
|
"set-user",
|
|
235
235
|
"microfunction",
|
|
236
|
-
"process"
|
|
236
|
+
"process",
|
|
237
|
+
"balance"
|
|
237
238
|
],
|
|
238
239
|
"type": "string",
|
|
239
240
|
"format": ""
|
|
@@ -349,6 +350,10 @@
|
|
|
349
350
|
"type": "string",
|
|
350
351
|
"nullable": true
|
|
351
352
|
},
|
|
353
|
+
"balanceOperation": {
|
|
354
|
+
"type": "string",
|
|
355
|
+
"nullable": true
|
|
356
|
+
},
|
|
352
357
|
"queryId": {
|
|
353
358
|
"type": "string",
|
|
354
359
|
"format": "uuid",
|
|
@@ -254,6 +254,10 @@
|
|
|
254
254
|
"push-notification",
|
|
255
255
|
"mobile-build",
|
|
256
256
|
"mobile-runner",
|
|
257
|
+
"mobile-store-product",
|
|
258
|
+
"mobile-store-import",
|
|
259
|
+
"app-purchase-import",
|
|
260
|
+
"mobile-store-diff",
|
|
257
261
|
"organization-schema",
|
|
258
262
|
"organization-unit",
|
|
259
263
|
"organization-position",
|
|
@@ -171,7 +171,8 @@
|
|
|
171
171
|
"revert",
|
|
172
172
|
"set-user",
|
|
173
173
|
"microfunction",
|
|
174
|
-
"process"
|
|
174
|
+
"process",
|
|
175
|
+
"balance"
|
|
175
176
|
],
|
|
176
177
|
"type": "string",
|
|
177
178
|
"format": ""
|
|
@@ -287,6 +288,10 @@
|
|
|
287
288
|
"type": "string",
|
|
288
289
|
"nullable": true
|
|
289
290
|
},
|
|
291
|
+
"balanceOperation": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"nullable": true
|
|
294
|
+
},
|
|
290
295
|
"queryId": {
|
|
291
296
|
"type": "string",
|
|
292
297
|
"format": "uuid",
|
|
@@ -76,7 +76,8 @@
|
|
|
76
76
|
"revert",
|
|
77
77
|
"set-user",
|
|
78
78
|
"microfunction",
|
|
79
|
-
"process"
|
|
79
|
+
"process",
|
|
80
|
+
"balance"
|
|
80
81
|
],
|
|
81
82
|
"type": "string",
|
|
82
83
|
"format": ""
|
|
@@ -192,6 +193,10 @@
|
|
|
192
193
|
"type": "string",
|
|
193
194
|
"nullable": true
|
|
194
195
|
},
|
|
196
|
+
"balanceOperation": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"nullable": true
|
|
199
|
+
},
|
|
195
200
|
"queryId": {
|
|
196
201
|
"type": "string",
|
|
197
202
|
"format": "uuid",
|
|
@@ -233,7 +233,8 @@
|
|
|
233
233
|
"revert",
|
|
234
234
|
"set-user",
|
|
235
235
|
"microfunction",
|
|
236
|
-
"process"
|
|
236
|
+
"process",
|
|
237
|
+
"balance"
|
|
237
238
|
],
|
|
238
239
|
"type": "string",
|
|
239
240
|
"format": ""
|
|
@@ -349,6 +350,10 @@
|
|
|
349
350
|
"type": "string",
|
|
350
351
|
"nullable": true
|
|
351
352
|
},
|
|
353
|
+
"balanceOperation": {
|
|
354
|
+
"type": "string",
|
|
355
|
+
"nullable": true
|
|
356
|
+
},
|
|
352
357
|
"queryId": {
|
|
353
358
|
"type": "string",
|
|
354
359
|
"format": "uuid",
|
package/dist/server.js
CHANGED
|
@@ -54,6 +54,8 @@ import { registerBpmReportingTools } from "./tools/bpmReporting.js";
|
|
|
54
54
|
import { registerModuleTools } from "./tools/modules.js";
|
|
55
55
|
import { registerMobileTools } from "./tools/mobile.js";
|
|
56
56
|
import { registerMobileStoreTools } from "./tools/mobileStore.js";
|
|
57
|
+
import { registerMobilePurchaseTools } from "./tools/mobilePurchases.js";
|
|
58
|
+
import { registerMobileStoreImportTools } from "./tools/mobileStoreImport.js";
|
|
57
59
|
import { registerAssistantTools } from "./tools/assistant.js";
|
|
58
60
|
const moduleRegistrations = {
|
|
59
61
|
platform: registerPlatformTools,
|
|
@@ -111,6 +113,8 @@ const moduleRegistrations = {
|
|
|
111
113
|
mobile: (server, platform) => {
|
|
112
114
|
registerMobileTools(server, platform);
|
|
113
115
|
registerMobileStoreTools(server, platform);
|
|
116
|
+
registerMobilePurchaseTools(server, platform);
|
|
117
|
+
registerMobileStoreImportTools(server, platform);
|
|
114
118
|
registerMobilePublishPrompt(server);
|
|
115
119
|
},
|
|
116
120
|
assistant: registerAssistantTools,
|
|
@@ -113,7 +113,7 @@ export function registerApplicationTools(server, platform) {
|
|
|
113
113
|
}));
|
|
114
114
|
server.registerTool('application_update', {
|
|
115
115
|
title: 'Application güncelle',
|
|
116
|
-
description: `Application bilgilerini günceller; homepageId zorunludur (mevcut değeri application_get ile alın). ${templateSubscriptionLock}`,
|
|
116
|
+
description: `Application bilgilerini günceller; homepageId zorunludur (mevcut değeri application_get ile alın). ORTAM (K5, b-15): ayar alanları — properties (mobile.offline, sekmeler, geçiş…), homepageId, notFoundPageId — yalnız development'a yazılır; production onları deployment_start ile alır (dal ortamları açıldıkları andaki kopyayı tutar), yani ayarı önce development'ta deneyin. Kimlik alanları (name, slug, color, dns) bütün ortamlara hemen gider. Yanıttaki environmentNote bunu çağrı başına söyler. ${templateSubscriptionLock}`,
|
|
117
117
|
inputSchema: {
|
|
118
118
|
id: guid,
|
|
119
119
|
slug: z.string().min(1),
|
|
@@ -132,7 +132,12 @@ export function registerApplicationTools(server, platform) {
|
|
|
132
132
|
: [];
|
|
133
133
|
const response = await platform.org('PUT', `/applications/${id}`, { body });
|
|
134
134
|
forgetApplication(platform, id);
|
|
135
|
-
|
|
135
|
+
// Uç tam kaydı yazar: verilmeyen properties/notFoundPageId development'ta da boşalır (APP-001, inceleme #4).
|
|
136
|
+
const missing = ['properties', 'notFoundPageId'].filter((field) => body[field] === undefined);
|
|
137
|
+
return {
|
|
138
|
+
...withPlatformWarnings(response, warnings),
|
|
139
|
+
environmentNote: `properties, homepageId ve notFoundPageId verildiği gibi yalnız development'a (ve Core'a) yazıldı; production onları deployment_start ile alır, dal ortamları hiç almaz (APP-001). name/slug/color/dns bütün ortamlara gitti.${missing.length > 0 ? ` ${missing.join(' ve ')} verilmedi: uç tam kaydı yazar, development'taki değer boşaltıldı — korumak için application_get'teki mevcut değeri gönderin.` : ''}`
|
|
140
|
+
};
|
|
136
141
|
}));
|
|
137
142
|
server.registerTool('application_delete', {
|
|
138
143
|
title: 'Application sil',
|
|
@@ -51,7 +51,7 @@ export function registerDeploymentTools(server, platform) {
|
|
|
51
51
|
}));
|
|
52
52
|
server.registerTool('deployment_status', {
|
|
53
53
|
title: 'Deployment durumu',
|
|
54
|
-
description: "Projenin son deployment durumunu döner. 'projectId' ZORUNLUDUR (project_list/project_get'ten alın) — verilmezse çağrı MCP şema katmanında reddedilir, platforma hiç gitmez. Hiç deployment yapılmamış projede yanıt BOŞ gelir; o durumda tool 'hiç deployment kaydı yok' bilgisini açıkça döner, yani boş yanıtı 'tool çalışmadı' diye okumayın. Durum satırı modül başına ayrılmaz: süreç tanımları ve iş takvimleri sayfa ve workflow'lardan sonra aynı saga adımında aktarılır; hedefte çalışamayan (eksik workflow/entity/rol) tanım aktarımı DefinitionInvalid ile düşürür. type 'error' ise 'error' alanı düşüş nedenini taşır (DefinitionInvalid'de bulgular 'yol: mesaj | …' olarak eklenir); aktarım atomiktir, hedef değişmemiştir. type 'completed-with-mismatch': deploy tamamlandı, kayıtlar production'a yazıldı, ama ardından koşan özet denetimi production'da fark buldu; sonraki deploy otomatik tam (full) koşar. Yanıt mode (full | delta: deploy'un gerçekte koştuğu kip) ve counters (delta'da modül başına added/updated/deleted) taşır.",
|
|
54
|
+
description: "Projenin son deployment durumunu döner. Bittikten sonra iki ortamın tanım farkını environment_diff verir (özetten; tablo satırları hariç). 'projectId' ZORUNLUDUR (project_list/project_get'ten alın) — verilmezse çağrı MCP şema katmanında reddedilir, platforma hiç gitmez. Hiç deployment yapılmamış projede yanıt BOŞ gelir; o durumda tool 'hiç deployment kaydı yok' bilgisini açıkça döner, yani boş yanıtı 'tool çalışmadı' diye okumayın. Durum satırı modül başına ayrılmaz: süreç tanımları ve iş takvimleri sayfa ve workflow'lardan sonra aynı saga adımında aktarılır; hedefte çalışamayan (eksik workflow/entity/rol) tanım aktarımı DefinitionInvalid ile düşürür. type 'error' ise 'error' alanı düşüş nedenini taşır (DefinitionInvalid'de bulgular 'yol: mesaj | …' olarak eklenir); aktarım atomiktir, hedef değişmemiştir. type 'completed-with-mismatch': deploy tamamlandı, kayıtlar production'a yazıldı, ama ardından koşan özet denetimi production'da fark buldu; sonraki deploy otomatik tam (full) koşar. Yanıt mode (full | delta: deploy'un gerçekte koştuğu kip) ve counters (delta'da modül başına added/updated/deleted) taşır.",
|
|
55
55
|
inputSchema: { projectId: guid },
|
|
56
56
|
annotations: readOnly
|
|
57
57
|
}, handle(config, async ({ projectId }) => {
|
|
@@ -2,7 +2,7 @@ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEvent
|
|
|
2
2
|
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, callerSlots, planEmulatedOps, recordsWithEventRemovals, serverOpsInOrder, assertOpBodies, applySkeletonOps, reshapesSkeleton } from './schemaOps.js';
|
|
3
3
|
import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
|
|
4
4
|
import { fetchHost, hostPath } from './screens.js';
|
|
5
|
-
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
5
|
+
import { assertScreenWriteGuards, screenCatalogNotes, unknownPropOps } from './screenWrite.js';
|
|
6
6
|
import { emptyWriteDiff, schemaOpDrops, withWriteDiff } from '../platform/writeDiff.js';
|
|
7
7
|
import { fetchComponentEventNames } from './components.js';
|
|
8
8
|
import { boxSectionNote, collectIds, ensureBoxSections, expandSectionToggles } from '../knowledge/boxSections.js';
|
|
@@ -10,7 +10,7 @@ import { PlatformApiError } from '../platform/errors.js';
|
|
|
10
10
|
import { connectDataOpIssues, connectedPropKeys } from '../knowledge/connectDataDefinition.js';
|
|
11
11
|
import { handle } from '../platform/responses.js';
|
|
12
12
|
import { slotWarning } from './layouts.js';
|
|
13
|
-
import { batchEnvelopeNote, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
|
|
13
|
+
import { batchEnvelopeNote, batchIndexHint, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
|
|
14
14
|
import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
|
|
15
15
|
import { boundFlowSteps, layoutPatchWarnings, screenProfileWarnings } from './profileWrite.js';
|
|
16
16
|
import { enforceTextHooks, textHookOverrideParam } from './textHookGuard.js';
|
|
@@ -87,6 +87,11 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
87
87
|
const events = host === 'layout' ? record.events : undefined;
|
|
88
88
|
const normalized = normalizeSchemaOps(opRecords);
|
|
89
89
|
assertOpBodies(opRecords, propTypeOps);
|
|
90
|
+
// g-21: tanınmayan prop sayfa yamasındaki gibi istek gitmeden op'uyla adlandırılır; yama bütün reddedilir.
|
|
91
|
+
const unknownProps = await unknownPropOps(platform, project, environment, applicationId, async () => nodes, allOps);
|
|
92
|
+
if (unknownProps.length > 0) {
|
|
93
|
+
throw new PlatformApiError(`${spec.tool}: tanınmayan prop (unknown-prop) — yama atomik olduğu için HİÇBİR op gönderilmedi (istek gönderilmedi): ${unknownProps.map(issue => issue.message).join(' | ')}${batchIndexHint(opened)}`, 400);
|
|
94
|
+
}
|
|
90
95
|
// 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.
|
|
91
96
|
const textFindings = await enforceTextHooks(config, {
|
|
92
97
|
platform, project, environment, applicationId,
|
|
@@ -18,6 +18,46 @@ export function registerEnvironmentTools(server, platform) {
|
|
|
18
18
|
inputSchema: { projectId: guid },
|
|
19
19
|
annotations: readOnly
|
|
20
20
|
}, handle(config, ({ projectId }) => platform.org('GET', `/environments/${projectId}`)));
|
|
21
|
+
server.registerTool('environment_diff', {
|
|
22
|
+
title: 'İki ortamın tanım farkı',
|
|
23
|
+
description: "İki ortamın (varsayılan development → production) TANIMLARINI karşılaştırır ve farklı olanları modül modül listeler: fark türü onlyInSource (yalnız kaynakta, dağıtılmamış) · onlyInTarget (yalnız hedefte) · changed (içerik farklı). Dağıtımdan sonra (deployment_status bitti dedikten sonra) 'production development'la aynı mı' sorusunun tek çağrılık cevabıdır. Fark tanım özetinden (definition digest) gelir, gövdeden değil: sırasız diziler ve çoğu üretilmiş alt kimlik karşılaştırmaya girmez — AMA akış adım parametrelerinin kimlikleri girer: development'ta yeniden yazılan (flow_update) bir akış içeriği aynı olsa da changed görünebilir; dağıtım kimlikleri koruduğu için dağıtımdan hemen sonraki karşılaştırma bundan etkilenmez. Kapsamı özetlenen modüllerdir (uygulama, rol, sorgu, DTO, görünüm, workflow, servis, klasör, layout, sayfa, akış, değişken, fonksiyon, mikrofonksiyon, çeviri); tablolar, bileşenler, erişim politikaları, süreçler, dosyalar ve asistan KAPSANMAZ — yanıtın notCovered alanı bu modülleri sayar, 'eşit' cevabı onlar için değildir. Tablo SATIRLARI (recordTransferMode) karşılaştırılmaz. En çok 50 kayıt satırı döner; fazlası truncated: true. Salt okunur; hiçbir ortama yazmaz.",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
projectId: guid,
|
|
26
|
+
source: z.string().min(1).optional().describe("Kaynak ortam adı; varsayılan 'development'"),
|
|
27
|
+
target: z.string().min(1).optional().describe("Hedef ortam adı; varsayılan 'production'"),
|
|
28
|
+
modules: z.array(z.string().min(1)).max(20).optional().describe("Yalnız bu modüller, backend'in modül adıyla (yanıtın modules[].module değerleri; ör. 'page', 'flow', 'microservice', 'data_transfer_object', 'global-function')")
|
|
29
|
+
},
|
|
30
|
+
annotations: readOnly
|
|
31
|
+
}, handle(config, async ({ projectId, source = 'development', target = 'production', modules }) => {
|
|
32
|
+
// Tek modül ve 50 satır sınırı sunucuda uygulanır (adlar yalnız listelenen kayıtlar için okunur); birden çok modül
|
|
33
|
+
// süzgecinde sunucu tavanı istenir, süzme burada yapılır.
|
|
34
|
+
const single = modules?.length === 1 ? modules[0] : undefined;
|
|
35
|
+
const response = await platform.org('GET', `/revisions/${projectId}/environment-diff`, {
|
|
36
|
+
query: { source, target, ...(single ? { module: single } : {}), limit: modules && modules.length > 1 ? 1000 : 50 }
|
|
37
|
+
});
|
|
38
|
+
const diff = (response?.data ?? response ?? {});
|
|
39
|
+
const wanted = (module) => modules === undefined || modules.includes(module);
|
|
40
|
+
const records = (diff.records ?? []).filter((record) => wanted(record.module));
|
|
41
|
+
const total = modules && modules.length > 1 ? records.length : Math.max(diff.total ?? 0, records.length);
|
|
42
|
+
const moduleCounts = (diff.modules ?? []).filter((module) => wanted(module.module));
|
|
43
|
+
return {
|
|
44
|
+
source,
|
|
45
|
+
target,
|
|
46
|
+
equal: total === 0,
|
|
47
|
+
...(total === 0 ? { note: `${source} ve ${target} özetlenen modüllerde eşit — notCovered'daki modüller karşılaştırılmadı.` } : {}),
|
|
48
|
+
modules: moduleCounts,
|
|
49
|
+
records: records.slice(0, 50).map((record) => ({
|
|
50
|
+
module: record.module,
|
|
51
|
+
...(record.name ? { name: record.name } : {}),
|
|
52
|
+
id: record.recordId,
|
|
53
|
+
change: record.state === 'different' ? 'changed' : record.state
|
|
54
|
+
})),
|
|
55
|
+
...(total > 50 || diff.truncated === true ? { truncated: true, total, truncatedNote: "50 satırdan fazla fark var — modules ile süzün (ör. modules: ['page'])." } : {}),
|
|
56
|
+
notCovered: diff.notCovered ?? [],
|
|
57
|
+
recordsNote: 'Tablo satırları (recordTransferMode) karşılaştırılmaz; bu yalnız tanım farkıdır.',
|
|
58
|
+
...(diff.digestCheck?.possiblyStale ? { digestNote: 'Ortamlardan birinin özeti hiç denetlenmemiş, son denetimi düşmüş ya da güvenilir değil: fark bayat olabilir.' } : {})
|
|
59
|
+
};
|
|
60
|
+
}));
|
|
21
61
|
server.registerTool('environment_create', {
|
|
22
62
|
title: 'Dal environment\'ı aç',
|
|
23
63
|
description: `Projede yeni bir dal environment'ı açar: kendi PG şeması kurulur ve development'ın tanımları oraya kopyalanır. Asenkron bir saga'dır — yanıt 202 döner, dal hemen kullanılabilir DEĞİLDİR. includeRecords true ise projenin KENDİ veritabanındaki bütün veri tablolarının satırları da kopyalanır (çok dilli companion tablolar ve junction'lar dahil; files/folders ve audit trail tabloları hariç, dış datasource'lara dokunulmaz) — entity'de kayıt taşıması ayarı açık olmasa bile. Merge'de yalnız kayıt taşıması ayarı açık tabloların satırları üç yollu geri taşınır (rehber: merge, Tablo kayıtları). ${templateSubscriptionLock}`,
|
package/dist/tools/flows.js
CHANGED
|
@@ -17,6 +17,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
17
17
|
import { flowProfileWarnings } from './profileWrite.js';
|
|
18
18
|
import { enforceTextHooks } from './textHookGuard.js';
|
|
19
19
|
import { readMobileOffline } from '../knowledge/platform/mobileOffline.js';
|
|
20
|
+
import { restorePathWarnings, subscriptionDisclosureWarnings } from '../knowledge/platform/mobileRules.js';
|
|
21
|
+
import { balanceCatalogFindings, balanceWithoutSignInWarnings, purchaseCatalogFindings } from '../knowledge/mobileStore.js';
|
|
20
22
|
import { resolveProfile } from '../knowledge/platform/resolve.js';
|
|
21
23
|
import { jsonArray } from '../platform/jsonArray.js';
|
|
22
24
|
import { z } from 'zod';
|
|
@@ -62,6 +64,23 @@ function hostNodes(record) {
|
|
|
62
64
|
return record.schema ?? record.views ?? [];
|
|
63
65
|
}
|
|
64
66
|
const BINDINGS_UNAVAILABLE = 'Barındırıcı kaydı okunamadığı için olay bağları hesaplanamadı; akış listesi yine de günceldir.';
|
|
67
|
+
/**
|
|
68
|
+
* g-21 (D-21, plans/defter-saha-bulgulari): `flow_get` `host` olmadan da çalışır. Akış kimliği sahibini söylemez ve
|
|
69
|
+
* platformda kimlikten sahibe giden uç yoktur; kimlik sırayla sayfa → layout → komponent kaydı olarak okunur. Yalnız
|
|
70
|
+
* 404 sonrakine geçer — başka hata (yetki, 500) sahibi hakkında bir şey söylemez ve olduğu gibi döner.
|
|
71
|
+
*/
|
|
72
|
+
async function resolveFlowHost(platform, project, environment, applicationId, hostId) {
|
|
73
|
+
for (const host of ['page', 'layout', 'component']) {
|
|
74
|
+
try {
|
|
75
|
+
return { host, record: await fetchHost(platform, host, project, environment, applicationId, hostId) };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!(error instanceof PlatformApiError) || error.status !== 404)
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new PlatformApiError(`flow_get: bu kimlikte sayfa/layout/komponent yok (${hostId}, uygulama ${applicationId}). hostId akışın barındırıcısının id'sidir, akışın kendi id'si değil — page_list / layout_list / component_list ile doğrulayın ya da host'u açıkça verin.`, 404);
|
|
83
|
+
}
|
|
65
84
|
/**
|
|
66
85
|
* TB-126 / S2-36 — satır aksiyonu taşıyabilen komponentler. Bu tiplerin `actions` prop'unu çoğu
|
|
67
86
|
* zaman YÜKLEME AKIŞI çalışma zamanında yazar (aksiyon role göre eklenip çıkarılıyorsa başka yolu
|
|
@@ -399,6 +418,60 @@ async function uncachedReadWarnings(platform, applicationId, steps, errorSteps,
|
|
|
399
418
|
}
|
|
400
419
|
return warnings;
|
|
401
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* FLOW-046 katalog ayağı ve MOB-036 (g-02): yazılan akışta `balance-spend` varsa aynı katalog okumasından beslenir. Statik bakiye
|
|
423
|
+
* adı uygulamanın tüketilen ürünlerinde yoksa `block` (FLOW-045 ile aynı kalıp); tüketilen ürün var ama giriş ucu yoksa MOB-036
|
|
424
|
+
* uyarısı için uygulamanın microservice listesi okunur (okunamazsa susar).
|
|
425
|
+
*
|
|
426
|
+
* FLOW-045 katalog ayağı, MOB-034 ve MOB-042 (kök ADR 0027, plans/mobil-uygulama-ici-satin-alma g-01). Üçü de tanımda
|
|
427
|
+
* olmayan bilgiyi ister — uygulamanın ürün kataloğu (Core `mobile-store/{appId}/products`), ekranın öteki akışları ve
|
|
428
|
+
* sayfanın ağacı — ve yalnız yazılan akışta `purchase` adımı varken okunur. Katalogda olmayan statik anahtar `block`
|
|
429
|
+
* bulgusudur (`findings`; çağıran yazımdan ÖNCE `applyPolicyMode` + `enforcePolicy` ile uygular — CD-005 kalıbı), öteki
|
|
430
|
+
* ikisi uyarıdır. Web uygulamasında adım her zaman `unsupported` döner (barındırıcı satın alma taşımaz): katalog
|
|
431
|
+
* okunmaz, yalnız uyarı. Okunamayan katalog ya da akış listesi bulgu üretmez — bilmediğimizi ret diye söylemeyiz.
|
|
432
|
+
*/
|
|
433
|
+
async function purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, steps, errorSteps) {
|
|
434
|
+
const written = [...(Array.isArray(steps) ? steps : []), ...(Array.isArray(errorSteps) ? errorSteps : [])].filter(isRecord);
|
|
435
|
+
const purchases = written.filter(step => String(step.type ?? '') === 'purchase');
|
|
436
|
+
const spends = written.filter(step => String(step.type ?? '') === 'balance-spend');
|
|
437
|
+
if (purchases.length === 0 && spends.length === 0)
|
|
438
|
+
return { findings: [], warnings: [] };
|
|
439
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
440
|
+
if (resolved.known && resolved.profile.platform !== 'mobile') {
|
|
441
|
+
return {
|
|
442
|
+
findings: [],
|
|
443
|
+
warnings: [
|
|
444
|
+
...(purchases.length > 0 ? [`FLOW-045: '${String(purchases[0]?.stepId ?? purchases[0]?.name ?? '?')}' purchase adımı bu web uygulamasında her zaman 'unsupported' döner — uygulama içi satın alma yalnız mobil uygulamanın mağaza derlemesindedir (önizlemede deneme penceresi).`] : []),
|
|
445
|
+
...(spends.length > 0 ? [`FLOW-046: '${String(spends[0]?.stepId ?? spends[0]?.name ?? '?')}' balance-spend adımı bu web uygulamasında her zaman 'unsupported' döner — bakiye harcama yalnız mobil uygulamanın mağaza derlemesindedir (önizlemede deneme penceresi).`] : [])
|
|
446
|
+
]
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const products = await platform.org('GET', `/mobile-store/${applicationId}/products`)
|
|
450
|
+
.then(payload => {
|
|
451
|
+
const data = isRecord(payload) && 'data' in payload ? payload.data : payload;
|
|
452
|
+
return Array.isArray(data) ? data.filter(isRecord) : undefined;
|
|
453
|
+
}, () => undefined);
|
|
454
|
+
const otherFlows = purchases.length > 0
|
|
455
|
+
? await listHostFlows(platform, host, project, environment, applicationId, hostId, async () => hostRecord)
|
|
456
|
+
.then(flows => flows.filter(flow => String(flow.id ?? '').toLowerCase() !== flowId.toLowerCase()), () => undefined)
|
|
457
|
+
: undefined;
|
|
458
|
+
const microservices = spends.length > 0 && products
|
|
459
|
+
? await platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query: { pageLength: 200 } })
|
|
460
|
+
.then(payload => {
|
|
461
|
+
const data = isRecord(payload) && 'data' in payload ? payload.data : payload;
|
|
462
|
+
const items = isRecord(data) && Array.isArray(data.items) ? data.items : data;
|
|
463
|
+
return Array.isArray(items) ? items.filter(isRecord) : undefined;
|
|
464
|
+
}, () => undefined)
|
|
465
|
+
: undefined;
|
|
466
|
+
return {
|
|
467
|
+
findings: products ? [...purchaseCatalogFindings(purchases, products), ...balanceCatalogFindings(spends, products)] : [],
|
|
468
|
+
warnings: [
|
|
469
|
+
...(otherFlows ? restorePathWarnings(written, otherFlows, products) : []),
|
|
470
|
+
...(products && purchases.length > 0 && host === 'page' ? subscriptionDisclosureWarnings(written, products, hostNodes(hostRecord), otherFlows ?? []) : []),
|
|
471
|
+
...(products && spends.length > 0 ? balanceWithoutSignInWarnings(products, microservices) : [])
|
|
472
|
+
]
|
|
473
|
+
};
|
|
474
|
+
}
|
|
402
475
|
/**
|
|
403
476
|
* FLOW-038 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
|
|
404
477
|
* kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
|
|
@@ -473,16 +546,26 @@ export function registerFlowTools(server, platform) {
|
|
|
473
546
|
server.registerTool('flow_get', {
|
|
474
547
|
title: 'Akış getir',
|
|
475
548
|
description: "Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur. bindings bu akışa bağlı olaylardır ([{ target: elementId | 'page', event }]; boşsa akış sahipsizdir). Hedefi silinmiş (element'siz) setter iteratee'si varsa yanıttaki flowWarnings söyler — o adım koşar ama hiçbir şeye yazmaz.",
|
|
476
|
-
inputSchema: {
|
|
549
|
+
inputSchema: {
|
|
550
|
+
project: projectParam,
|
|
551
|
+
environment: environmentParam,
|
|
552
|
+
host: hostParam.optional().describe("Akışın barındırıcısı: page | layout | component. Verilmezse gateway hostId'yi sırayla sayfa → layout → komponent olarak okur (en çok üç okuma, 404 sonrakine geçer) ve yanıtın hostResolvedAs alanı bulduğunu söyler; biliyorsanız verin, tek okuma yapılır"),
|
|
553
|
+
applicationId: guid,
|
|
554
|
+
hostId: guid,
|
|
555
|
+
flowId: flowIdParam
|
|
556
|
+
},
|
|
477
557
|
annotations: readOnly
|
|
478
558
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
479
|
-
const
|
|
480
|
-
const
|
|
559
|
+
const resolved = host === undefined ? await resolveFlowHost(platform, project, environment, applicationId, hostId) : undefined;
|
|
560
|
+
const owner = resolved?.host ?? host;
|
|
561
|
+
const response = await platform.tenant('GET', project, flowPath(owner, applicationId, hostId, flowId), { environment });
|
|
562
|
+
const record = resolved?.record ?? await fetchHost(platform, owner, project, environment, applicationId, hostId).catch(() => undefined);
|
|
481
563
|
const warnings = targetlessSetterWarnings(response);
|
|
482
564
|
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
483
565
|
return response;
|
|
484
566
|
return {
|
|
485
567
|
...response,
|
|
568
|
+
...(resolved === undefined ? {} : { hostResolvedAs: resolved.host }),
|
|
486
569
|
...(record === undefined
|
|
487
570
|
? { bindingsNote: BINDINGS_UNAVAILABLE }
|
|
488
571
|
: { bindings: flowBindings(hostNodes(record), record.events).get(flowId.toLowerCase()) ?? [] }),
|
|
@@ -534,6 +617,8 @@ export function registerFlowTools(server, platform) {
|
|
|
534
617
|
where: `akış '${name}'`
|
|
535
618
|
}, policyOverride);
|
|
536
619
|
const readService = serviceDefinitionReader(platform, project, environment, applicationId);
|
|
620
|
+
const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, writtenFlowId, steps, errorSteps ?? []);
|
|
621
|
+
const purchaseFindings = enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
|
|
537
622
|
const parameterWarnings = [
|
|
538
623
|
...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
|
|
539
624
|
...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
|
|
@@ -541,7 +626,8 @@ export function registerFlowTools(server, platform) {
|
|
|
541
626
|
...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
|
|
542
627
|
...microserviceAlwaysDataWarnings(steps, errorSteps ?? []),
|
|
543
628
|
...await queuedBranchWarnings(platform, applicationId, steps, errorSteps ?? [], readService),
|
|
544
|
-
...await uncachedReadWarnings(platform, applicationId, steps, errorSteps ?? [], readService)
|
|
629
|
+
...await uncachedReadWarnings(platform, applicationId, steps, errorSteps ?? [], readService),
|
|
630
|
+
...purchase.warnings
|
|
545
631
|
];
|
|
546
632
|
// Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
|
|
547
633
|
// ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
|
|
@@ -569,7 +655,7 @@ export function registerFlowTools(server, platform) {
|
|
|
569
655
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
570
656
|
...(platformWarnings.length > 0 ? { platformWarnings } : {}),
|
|
571
657
|
hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
572
|
-
}, normalized), drops.renamed), drops), [...findings, ...textFindings]);
|
|
658
|
+
}, normalized), drops.renamed), drops), [...findings, ...textFindings, ...purchaseFindings]);
|
|
573
659
|
}));
|
|
574
660
|
server.registerTool('flow_update', {
|
|
575
661
|
title: 'Akış güncelle',
|
|
@@ -624,6 +710,9 @@ export function registerFlowTools(server, platform) {
|
|
|
624
710
|
}, policyOverride)
|
|
625
711
|
: [];
|
|
626
712
|
const readService = serviceDefinitionReader(platform, project, environment, applicationId);
|
|
713
|
+
const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, flow.steps, flow.errorSteps);
|
|
714
|
+
// Kayıtlı gövde suçlanmaz: yalnız ad/slug değişen yazımda katalog bulgusu uygulanmaz (FLOW-* kuralları gibi).
|
|
715
|
+
const purchaseFindings = enforcePolicy(config.policyMode === 'off' || !(steps || errorSteps) ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
|
|
627
716
|
const parameterWarnings = [
|
|
628
717
|
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
629
718
|
...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
|
|
@@ -631,7 +720,8 @@ export function registerFlowTools(server, platform) {
|
|
|
631
720
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
|
|
632
721
|
...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
|
|
633
722
|
...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
|
|
634
|
-
...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
|
|
723
|
+
...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
|
|
724
|
+
...purchase.warnings
|
|
635
725
|
];
|
|
636
726
|
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
|
|
637
727
|
// Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
|
|
@@ -644,7 +734,7 @@ export function registerFlowTools(server, platform) {
|
|
|
644
734
|
});
|
|
645
735
|
const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
|
|
646
736
|
return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
647
|
-
...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), [...findings, ...textFindings]);
|
|
737
|
+
...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), [...findings, ...textFindings, ...purchaseFindings]);
|
|
648
738
|
}));
|
|
649
739
|
server.registerTool('flow_patch', {
|
|
650
740
|
title: "Akışa patch uygula",
|
|
@@ -690,6 +780,8 @@ export function registerFlowTools(server, platform) {
|
|
|
690
780
|
}, policyOverride)
|
|
691
781
|
];
|
|
692
782
|
const readService = serviceDefinitionReader(platform, project, environment, applicationId);
|
|
783
|
+
const purchase = await purchaseChecks(platform, host, project, environment, applicationId, hostId, hostRecord, flowId, flow.steps, flow.errorSteps);
|
|
784
|
+
const purchaseFindings = enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, purchase.findings, policyOverride));
|
|
693
785
|
const parameterWarnings = [
|
|
694
786
|
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
695
787
|
...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
|
|
@@ -697,7 +789,8 @@ export function registerFlowTools(server, platform) {
|
|
|
697
789
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
|
|
698
790
|
...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
|
|
699
791
|
...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
|
|
700
|
-
...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
|
|
792
|
+
...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
|
|
793
|
+
...purchase.warnings
|
|
701
794
|
];
|
|
702
795
|
// Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
|
|
703
796
|
const netOps = fastJsonPatch.compare(current, flow);
|
|
@@ -717,7 +810,7 @@ export function registerFlowTools(server, platform) {
|
|
|
717
810
|
flow: flowSummary(written.data),
|
|
718
811
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
719
812
|
...(platformWarnings.length > 0 ? { platformWarnings } : {})
|
|
720
|
-
}, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), findings);
|
|
813
|
+
}, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), [...findings, ...purchaseFindings]);
|
|
721
814
|
}));
|
|
722
815
|
server.registerTool('flow_delete', {
|
|
723
816
|
title: 'Akış sil',
|
package/dist/tools/mobile.js
CHANGED
|
@@ -114,6 +114,57 @@ export function registerMobileTools(server, platform) {
|
|
|
114
114
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid.describe('Mobil uygulama (slug da verilebilir, gateway GUID\'e çevirir)'), ...pushAudienceFields, pageIndex: pagingParams.pageIndex, pageLength: pagingParams.pageLength.describe('Sayfa boyu; varsayılan 25') },
|
|
115
115
|
annotations: readOnly
|
|
116
116
|
}, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/push/devices/${applicationId}`, { environment, query })));
|
|
117
|
+
server.registerTool('mobile_error_list', {
|
|
118
|
+
title: 'Telefonda düşen akış hataları',
|
|
119
|
+
description: "Mobil uygulamanın TELEFONDA düşen akış hatalarını okur (kabuk gönderir, sunucu saklar — defter-saha-bulgulari b-10/f-10). Kayıt biçimi page_screenshot.runtimeErrors ile aynıdır (flowId · step{id,name,type} · code · message · element · property · kind) ve şunları ekler: pageSlug, platform, appVersion, devices (kaç kurulumda), count, lastAt. Aynı akış/adım/mesaj üçlüsü tek satıra katlanır, en yeni önce. Önizlemede görülmeyen cihaz hatasını (ağsız yazma, sayfa kapanınca atlanan adım `page-unmounted`, geri gidilemeyen `back-unavailable`) kullanıcının anlatmasını beklemeden okumak için. Kabuk hatayı ağ gelince gönderir: uçak kipinde düşen hata ağ dönene kadar görünmez; occurredAt telefondaki saattir. Sunucu kurulum başına son 200 kaydı ve 7 günü tutar; mesaj gateway maskesinden geçer. Uygulama ayarı properties.mobile.reportErrors: false gönderimi kapatır. Web önizlemesinin hataları burada YOK — onları page_screenshot.runtimeErrors verir.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
project: projectParam,
|
|
122
|
+
environment: environmentParam,
|
|
123
|
+
applicationId: guid.describe('Mobil uygulama (slug da verilebilir, gateway GUID\'e çevirir)'),
|
|
124
|
+
pageSlug: z.string().min(1).optional().describe('Yalnız bu sayfada düşenler'),
|
|
125
|
+
flowId: z.string().min(1).optional().describe('Yalnız bu akış'),
|
|
126
|
+
installationId: z.string().min(1).optional().describe('Yalnız bu kurulum (notification_device_list\'teki installationId ile aynı kimlik)'),
|
|
127
|
+
since: z.string().min(1).optional().describe('ISO zaman; yalnız bundan sonra görülenler'),
|
|
128
|
+
limit: z.number().int().min(1).max(100).optional().describe('Katlanmış satır sayısı; varsayılan 20')
|
|
129
|
+
},
|
|
130
|
+
annotations: readOnly
|
|
131
|
+
}, handle(config, async ({ project, environment, applicationId, limit = 20, ...query }) => {
|
|
132
|
+
let response;
|
|
133
|
+
try {
|
|
134
|
+
response = await platform.tenant('GET', project, `/runtime-errors/${applicationId}`, { environment, query: { ...query, limit: 200 } });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof PlatformApiError && error.status === 404) {
|
|
138
|
+
throw new PlatformApiError('mobile_error_list: bu backend istemci akış hatası kaydetmiyor (runtime-errors ucu yok) — backend imajı güncel değil ya da uç bu kurulumda kapalı. Önizleme hatalarını page_screenshot.runtimeErrors verir.', 404);
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
const rows = unwrap(response) ?? [];
|
|
143
|
+
// Aynı akış/adım/mesaj tek satır: sayılar toplanır, en yeni kaydın bağlamı (sayfa, sürüm, platform) taşınır.
|
|
144
|
+
const folded = new Map();
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
const key = `${row.flowId ?? ''}|${row.step?.id ?? ''}|${row.message ?? ''}`;
|
|
147
|
+
const lastAt = row.lastAt ?? row.occurredAt ?? '';
|
|
148
|
+
const existing = folded.get(key);
|
|
149
|
+
if (!existing) {
|
|
150
|
+
folded.set(key, { ...row, lastAt, count: row.count ?? 1, devices: new Set(row.installationId ? [row.installationId] : []) });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
existing.count = (existing.count ?? 1) + (row.count ?? 1);
|
|
154
|
+
if (row.installationId)
|
|
155
|
+
existing.devices.add(row.installationId);
|
|
156
|
+
if (lastAt > (existing.lastAt ?? ''))
|
|
157
|
+
Object.assign(existing, { ...row, lastAt, count: existing.count, devices: existing.devices });
|
|
158
|
+
}
|
|
159
|
+
const errors = [...folded.values()]
|
|
160
|
+
.sort((left, right) => (right.lastAt ?? '').localeCompare(left.lastAt ?? ''))
|
|
161
|
+
.map(({ devices, installationId: _installation, ...error }) => ({ ...error, devices: devices.size }));
|
|
162
|
+
return {
|
|
163
|
+
errors: errors.slice(0, limit),
|
|
164
|
+
...(errors.length > limit ? { truncated: true, total: errors.length } : {}),
|
|
165
|
+
...(errors.length === 0 ? { note: 'Bu süzgeçle kayıt yok. Telefon hatayı ağ gelince gönderir; uçak kipindeki hata ağ dönene kadar görünmez. Uygulama ayarı properties.mobile.reportErrors: false ise kabuk göndermez.' } : {})
|
|
166
|
+
};
|
|
167
|
+
}));
|
|
117
168
|
server.registerTool('notification_create', {
|
|
118
169
|
title: 'Push bildirimi gönder (varsayılan kuru koşu)',
|
|
119
170
|
description: "Kitleye dile göre içerikle push bildirimi gönderir — `SendPushNotificationCommand: { dryRun, audience, content, defaultLanguage, path?, query?, androidChannel?, badge?, testInstallationId? }`. dryRun VARSAYILAN TRUE: yalnız kitleyi sayar ({ total, byLanguage }), hiçbir şey yazmaz; göndermek için dryRun: false AÇIKÇA verilir. Gerçek gönderim kuyruğa girer ve yanıt id döner; sonucu notification_history { id } ile okuyun (birkaç saniye sonra sent/failed, teslim sayıları ve hatalı cihazlar). Kitle her zaman aktif ve izinli cihazlara daraltılır; testInstallationId verilirse yalnız o kurulum (notification_device_list'teki installationId). İçerik dil → { title, body }; başlığı boş dil düşer ve o cihaz varsayılan dili alır; varsayılan dilde başlık zorunlu (NTF-002, platform 400). Dil anahtarları küçük harfe indirilir. path dokunuşta açılacak sayfanın slug'ı, query onun sorgusu. Ret: production'da dryRun: false istek gitmeden reddedilir (NTF-001).",
|
|
@@ -169,6 +220,11 @@ export function registerMobileTools(server, platform) {
|
|
|
169
220
|
const plan = buildMobileAppPlan(input);
|
|
170
221
|
if (dryRun)
|
|
171
222
|
return { dryRun: true, plan };
|
|
223
|
+
// APP-001 (K5, b-15): sekme ve ana sayfa ayarı yalnız development'a yazılır. Dalda kurulan sayfalara bağlı ayar
|
|
224
|
+
// development'ta olmayan sayfaları gösterir, dal sekmesiz kalırdı.
|
|
225
|
+
if (platform.resolveEnvironment(environment) !== 'development') {
|
|
226
|
+
throw new PlatformApiError(`mobile_app_create [APP-001]: uygulama ayarı (sekmeler, ana sayfa) yalnız development'a yazılır; '${platform.resolveEnvironment(environment)}' ortamında kurulan sayfalara bağlanamaz. Mobil uygulamayı development'ta kurun, production'a deployment_start ile taşıyın. Hiçbir şey yazılmadı.`, 400);
|
|
227
|
+
}
|
|
172
228
|
const completed = [];
|
|
173
229
|
const skipped = new Set();
|
|
174
230
|
const warnings = [];
|