@appaflytech/zeyrek-ai-mcp 0.0.28 → 0.0.31
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/.env.example +15 -0
- package/README.md +5 -5
- package/dist/config.js +37 -0
- package/dist/index.js +2 -0
- package/dist/knowledge/access.js +802 -0
- package/dist/knowledge/backendConstraints.js +18 -1
- package/dist/knowledge/case.js +41 -0
- package/dist/knowledge/componentCatalog.js +500 -35
- package/dist/knowledge/componentEvents.js +132 -7
- package/dist/knowledge/connectDataBind.js +819 -0
- package/dist/knowledge/connectDataDefinition.js +341 -0
- package/dist/knowledge/connectDataExecute.js +93 -0
- package/dist/knowledge/connectDataRules.js +128 -0
- package/dist/knowledge/decision.js +121 -0
- package/dist/knowledge/expressionPrelude.js +59 -0
- package/dist/knowledge/layoutSummary.js +140 -2
- package/dist/knowledge/microserviceParameters.js +58 -0
- package/dist/knowledge/mining.js +94 -0
- package/dist/knowledge/mobileComponentCatalog.js +859 -0
- package/dist/knowledge/mobileComponentEvents.js +239 -0
- package/dist/knowledge/normalize.js +236 -67
- package/dist/knowledge/outlines.js +36 -0
- package/dist/knowledge/platform/lint.js +385 -0
- package/dist/knowledge/platform/mobile.js +142 -0
- package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
- package/dist/knowledge/platform/mobileRules.js +231 -0
- package/dist/knowledge/platform/mobileSpacing.js +82 -0
- package/dist/knowledge/platform/mobileTabs.js +108 -0
- package/dist/knowledge/platform/nativeBoxModel.js +44 -0
- package/dist/knowledge/platform/profile.js +25 -0
- package/dist/knowledge/platform/resolve.js +122 -0
- package/dist/knowledge/platform/web.js +39 -0
- package/dist/knowledge/policy.js +18 -4
- package/dist/knowledge/process.js +510 -0
- package/dist/knowledge/reservedTables.js +79 -0
- package/dist/knowledge/rowFilter.js +43 -0
- package/dist/knowledge/rules.js +366 -21
- package/dist/knowledge/schemaGuard.js +11 -0
- package/dist/knowledge/styleResolve.js +50 -4
- package/dist/knowledge/styleResolveNative.js +527 -0
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/observability/trace.js +6 -3
- package/dist/platform/argumentAliases.js +128 -0
- package/dist/platform/bodyFile.js +103 -0
- package/dist/platform/capture/actions.js +333 -0
- package/dist/platform/capture/browser.js +1 -0
- package/dist/platform/{screenshot.js → capture/capture.js} +105 -519
- package/dist/platform/capture/console-log.js +122 -0
- package/dist/platform/capture/index.js +11 -0
- package/dist/platform/capture/readiness.js +103 -0
- package/dist/platform/capture/summary.js +77 -0
- package/dist/platform/capture/target.js +5 -0
- package/dist/platform/capture/targets/studio-mobile.js +15 -0
- package/dist/platform/capture/targets/studio-web.js +5 -0
- package/dist/platform/diagnostics.js +10 -4
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/errors.js +162 -10
- package/dist/platform/http.js +29 -8
- package/dist/platform/impersonation.js +45 -0
- package/dist/platform/jsonArray.js +24 -0
- package/dist/platform/responses.js +39 -5
- package/dist/platform/schemaReadMemory.js +60 -0
- package/dist/platform/secrets.js +17 -1
- package/dist/platform/strictArguments.js +68 -12
- package/dist/platform/tokenVault.js +187 -4
- package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
- package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
- package/dist/resources/examples/bpm-satinalma.json +376 -0
- package/dist/resources/examples/connect-data-convert-verify.json +107 -0
- package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
- package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
- package/dist/resources/examples/connect-data-query-params.json +50 -0
- package/dist/resources/examples/connect-data-table-entity.json +147 -0
- package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
- package/dist/resources/examples/middleware-authenticate.json +1 -1
- package/dist/resources/examples/mobile-app-shell-drawer.json +243 -0
- package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
- package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
- package/dist/resources/examples/mobile-form-screen.json +365 -0
- package/dist/resources/examples/mobile-list-detail-form.json +672 -0
- package/dist/resources/examples/page-detail-profile.json +2 -1
- package/dist/resources/examples/page-dynamic-form.json +3 -2
- package/dist/resources/examples/page-flex-sections.json +1 -1
- package/dist/resources/examples/page-html-embed.json +1 -1
- package/dist/resources/examples/workflow-basic.json +8 -2
- package/dist/resources/guides/access.md +810 -0
- package/dist/resources/guides/bpm.md +691 -0
- package/dist/resources/guides/case.md +153 -0
- package/dist/resources/guides/component.md +92 -5
- package/dist/resources/guides/decision.md +185 -0
- package/dist/resources/guides/entity.md +50 -8
- package/dist/resources/guides/expressions.md +14 -5
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/getting-started.md +38 -1
- package/dist/resources/guides/layout.md +11 -7
- package/dist/resources/guides/merge.md +37 -3
- package/dist/resources/guides/microservice.md +87 -2
- package/dist/resources/guides/middleware.md +68 -4
- package/dist/resources/guides/mobile-layout.md +193 -0
- package/dist/resources/guides/mobile.md +495 -0
- package/dist/resources/guides/page.md +456 -75
- package/dist/resources/guides/theme.md +8 -2
- package/dist/resources/guides/translation.md +1 -1
- package/dist/resources/guides/workflow.md +58 -1
- package/dist/resources/read.js +41 -0
- package/dist/resources/rules.js +11 -5
- package/dist/resources/schema-fingerprint.json +7 -5
- package/dist/resources/schemas/AccessExplanationDto.json +144 -0
- package/dist/resources/schemas/AccessMatrixDto.json +170 -0
- package/dist/resources/schemas/AccessPermissionDto.json +57 -0
- package/dist/resources/schemas/AccessPolicyDto.json +223 -0
- package/dist/resources/schemas/AccessResourceDto.json +86 -0
- package/dist/resources/schemas/AppSessionDto.json +53 -0
- package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
- package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
- package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
- package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
- package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
- package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
- package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
- package/dist/resources/schemas/CreateEntityCommand.json +118 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
- package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
- package/dist/resources/schemas/CreateQueryCommand.json +4 -0
- package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
- package/dist/resources/schemas/DataJobDto.json +6 -1
- package/dist/resources/schemas/DecisionDto.json +302 -0
- package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
- package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
- package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +21 -1
- package/dist/resources/schemas/MicroserviceDto.json +15 -3
- package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
- package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
- package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
- package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
- package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
- package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
- package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
- package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
- package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
- package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
- package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
- package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
- package/dist/resources/schemas/StepDto.json +25 -1
- package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
- package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
- package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
- package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
- package/dist/resources/schemas/UpdateViewCommand.json +0 -2
- package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
- package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
- package/dist/server.js +27 -1
- package/dist/tools/access.js +369 -2
- package/dist/tools/accessOperations.js +135 -0
- package/dist/tools/accessPolicies.js +170 -0
- package/dist/tools/accessRuntime.js +203 -0
- package/dist/tools/applications.js +60 -3
- package/dist/tools/bpm.js +343 -0
- package/dist/tools/bpmGovernance.js +266 -0
- package/dist/tools/bpmOperations.js +230 -0
- package/dist/tools/bpmReporting.js +154 -0
- package/dist/tools/bpmRouting.js +150 -0
- package/dist/tools/bpmRuntime.js +396 -0
- package/dist/tools/case.js +178 -0
- package/dist/tools/caseRuntime.js +89 -0
- package/dist/tools/common.js +7 -0
- package/dist/tools/components.js +118 -14
- package/dist/tools/connectData.js +449 -0
- package/dist/tools/connectors.js +193 -0
- package/dist/tools/datasources.js +3 -3
- package/dist/tools/decisions.js +206 -0
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +38 -3
- package/dist/tools/designSchemaPatch.js +34 -9
- package/dist/tools/dtos.js +36 -5
- package/dist/tools/entities.js +79 -12
- package/dist/tools/externalComponents.js +26 -5
- package/dist/tools/flows.js +122 -14
- package/dist/tools/folders.js +1 -1
- package/dist/tools/imports.js +5 -2
- package/dist/tools/layouts.js +50 -17
- package/dist/tools/merges.js +2 -2
- package/dist/tools/microservices.js +77 -25
- package/dist/tools/mobile.js +221 -0
- package/dist/tools/modules.js +48 -0
- package/dist/tools/organizationStructure.js +137 -0
- package/dist/tools/pages.js +465 -98
- package/dist/tools/platformInfo.js +27 -8
- package/dist/tools/profileWrite.js +92 -0
- package/dist/tools/projects.js +71 -9
- package/dist/tools/rows.js +69 -13
- package/dist/tools/schemaOps.js +61 -1
- package/dist/tools/screenWrite.js +37 -8
- package/dist/tools/screens.js +95 -5
- package/dist/tools/templates.js +26 -1
- package/dist/tools/translations.js +1 -1
- package/dist/tools/variables.js +11 -5
- package/dist/tools/workflows.js +10 -5
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listKnowledgeFiles, readKnowledgeFile } from '../resources/knowledge.js';
|
|
2
|
+
import { readToolResource } from '../resources/read.js';
|
|
2
3
|
import { splitGuideSections } from '../knowledge/guideSections.js';
|
|
3
4
|
import { PlatformApiError } from '../platform/errors.js';
|
|
4
5
|
import { logCapabilityGap } from '../observability/trace.js';
|
|
@@ -63,8 +64,10 @@ const guideRouting = {
|
|
|
63
64
|
microservice: 'kaynak seçimi (table > query), selectFields disiplini',
|
|
64
65
|
microfunction: 'kendi runtime\'ında koşan Node/Python fonksiyonu: protokol, yayın döngüsü, step kullanımı',
|
|
65
66
|
entity: "alan tipleri, ilişkiler, hazır entity'ler",
|
|
66
|
-
page: 'sayfa/flow işleri; flex düzen prop grubu ve boyut modları (Kural 1f-2)',
|
|
67
|
-
layout: 'yerleşim kabuğu: tek slot, views,
|
|
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
|
+
layout: 'yerleşim kabuğu: tek slot, views, dar ekranda yığılma, widthType fill/fixed/auto',
|
|
69
|
+
mobile: "mobil UYGULAMA (type:'mobile'): kurulum sırası (mobile_app_create), flex-only yerleşim, tipli stil, sekmeler (properties.mobile.tabs) ve çekmece, adım sözlüğü, MOB-* bulguları, page_screenshot device",
|
|
70
|
+
'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ü',
|
|
68
71
|
component: 'komponent modeli, olay sözleşmeleri, appearance boyut sözlüğü (widthString/heightString)',
|
|
69
72
|
html: 'hazır komponent yetmediğinde tek seferlik kaçış kapısı (Mustache, fireEvent, script kuralları)',
|
|
70
73
|
'external-component': 'kalıcı ihtiyaçta kendi komponentini yazıp yayınlama',
|
|
@@ -73,7 +76,10 @@ const guideRouting = {
|
|
|
73
76
|
expressions: 'ifade fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…)',
|
|
74
77
|
functions: "fonksiyonlar, iki kaynak tek biçim: source:'studio' tarayıcıda Functions.<ad>() (kapsam uygulama/sayfa, UI flow 'function' adımı), source:'workflow' sunucuda çıplak ad; parametre/varsayılan/required, tuzaklar; GÖVDE GÜNCELLEME function_update (yalnız değişen alanı gönder, patch aracı yok)",
|
|
75
78
|
translation: 'sayfa/içerik çevirileri (çoklu dil)',
|
|
76
|
-
|
|
79
|
+
access: 'kompleks proje yetkisi: organizasyon ağacı, izin kataloğu, kapsam, grant/atama, alan koruması (maske · şifre · türetilmiş alan)',
|
|
80
|
+
merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları",
|
|
81
|
+
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)',
|
|
82
|
+
decision: 'DMN karar tabloları: model, isabet politikaları, FEEL alt kümesi, DRD, süreçte decision düğümü, DMN XML'
|
|
77
83
|
};
|
|
78
84
|
// page.md 123 KB; section'sız dönerse yanıt tool çıktı sınırını aşıyor ve rehber MCP kanalından
|
|
79
85
|
// HİÇ okunamıyor (27/05 E-1, 09-04 koşusu). Eşik ÖLÇÜLEN kopma noktasından türetilir, "bugün
|
|
@@ -92,7 +98,8 @@ export function registerPlatformTools(server, platform) {
|
|
|
92
98
|
'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
|
|
93
99
|
...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
|
|
94
100
|
"Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
|
|
95
|
-
'
|
|
101
|
+
"Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
|
|
102
|
+
"Örnek gövdeler ve kural katalogları (ngapps://examples/<ad>, ngapps://rules/<ad>) istemci MCP resource okuyamıyorsa da bu araçla okunur: platform_guide({ resource: 'ngapps://rules/connect-data' }) — guide verilmez; dizin için 'ngapps://examples/index'."
|
|
96
103
|
].join('\n');
|
|
97
104
|
server.registerTool('platform_whoami', {
|
|
98
105
|
title: 'Oturum bilgisi',
|
|
@@ -104,13 +111,23 @@ export function registerPlatformTools(server, platform) {
|
|
|
104
111
|
title: 'Platform rehberi oku',
|
|
105
112
|
description: guideDescription,
|
|
106
113
|
inputSchema: {
|
|
107
|
-
guide: (guides.length > 0 ? z.enum(guides) : z.string())
|
|
108
|
-
.describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir'),
|
|
114
|
+
guide: (guides.length > 0 ? z.enum(guides) : z.string()).optional()
|
|
115
|
+
.describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir. resource ile birlikte verilmez'),
|
|
116
|
+
resource: z.string().regex(/^ngapps:\/\//).optional()
|
|
117
|
+
.describe("guide yerine bir ngapps:// kaynağı okur (examples ve rules): 'ngapps://examples/connect-data-table-entity', 'ngapps://rules/connect-data', dizinler '…/index'. Resource okuyamayan istemcinin rehberdeki ngapps:// yönlendirmelerini izleme yolu budur (38/M-14)."),
|
|
109
118
|
section: z.string().optional()
|
|
110
119
|
.describe("Yalnız bu bölümü döner (yanıttaki 'sections' listesinden bir slug). Verilmezse rehber tam döner; ancak tool yanıt sınırını aşan büyük rehberlerde (page) yalnız giriş + outline döner")
|
|
111
120
|
},
|
|
112
121
|
annotations: readOnly
|
|
113
|
-
}, handle(config, async ({ guide, section }) => {
|
|
122
|
+
}, handle(config, async ({ guide, section, resource }) => {
|
|
123
|
+
if (resource !== undefined) {
|
|
124
|
+
if (guide !== undefined || section !== undefined)
|
|
125
|
+
throw new PlatformApiError("platform_guide: resource ile guide/section birlikte verilemez — biri ngapps:// kaynağını, diğeri rehber bölümünü okur.", 400);
|
|
126
|
+
const read = readToolResource(resource);
|
|
127
|
+
return 'error' in read ? read : { resource: read.uri, mimeType: read.mimeType, content: read.text };
|
|
128
|
+
}
|
|
129
|
+
if (guide === undefined)
|
|
130
|
+
throw new PlatformApiError("platform_guide: 'guide' (rehber adı) ya da 'resource' (ngapps:// kaynağı) verin.", 400);
|
|
114
131
|
const available = listKnowledgeFiles('guides').map(file => file.replace(/\.md$/, ''));
|
|
115
132
|
if (!available.includes(guide)) {
|
|
116
133
|
return { error: `'${guide}' diye bir rehber yok.`, available };
|
|
@@ -154,6 +171,7 @@ export function registerPlatformTools(server, platform) {
|
|
|
154
171
|
title: 'Kabiliyet boşluğu bildir',
|
|
155
172
|
description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide; hazır komponent yetmiyorsa tek seferlik için 'html' kaçış kapısı, kalıcı bir ihtiyaç içinse 'external-component' — kendi komponentinizi yazıp yayınlayabilirsiniz) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
|
|
156
173
|
inputSchema: {
|
|
174
|
+
project: z.string().min(1).optional().describe("Boşluğun görüldüğü proje (ad ya da slug); kayda yazılır. Bu çağrı platforma gitmez, proje yalnız bağlamdır"),
|
|
157
175
|
goal: z.string().min(1).optional().describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek"). Eşanlamlı kabul edilir: title · summary · what.'),
|
|
158
176
|
blocker: z.string().min(1).optional().describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok"). Eşanlamlı kabul edilir: reason · problem · missing · description.'),
|
|
159
177
|
severity: z.string().optional().describe("YOL HARİTASINDAKİ SIRAYI BU ALAN BELİRLER, doldurun. blocker: özellik hiç yapılamadı · workaround: daha kötü bir yolla yapıldı · cosmetic: yalnız görsel/ergonomik kayıp. Eşanlamlılar çevrilir (critical/high/blocking→blocker, medium/major→workaround, low/minor/nit→cosmetic). TANINMAYAN değer reddedilir; verilmezse kayıt düşer ama yanıt 'incomplete' ile uyarır ve bildirim orta kovaya yazılır."),
|
|
@@ -171,9 +189,10 @@ export function registerPlatformTools(server, platform) {
|
|
|
171
189
|
annotations: readOnly
|
|
172
190
|
}, handle(config, async (input) => {
|
|
173
191
|
const gap = normalizeGapReport(input);
|
|
174
|
-
logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround });
|
|
192
|
+
logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround, project: input.project });
|
|
175
193
|
return {
|
|
176
194
|
recorded: true,
|
|
195
|
+
...(input.project ? { project: input.project } : {}),
|
|
177
196
|
severity: gap.severity,
|
|
178
197
|
module: gap.module ?? null,
|
|
179
198
|
...(Object.keys(gap.normalizedFrom).length > 0 ? { normalizedFrom: gap.normalizedFrom } : {}),
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { findingLines, profileFindings, stateStorageFindings } from '../knowledge/platform/lint.js';
|
|
2
|
+
import { describeShell, mobileLayoutFindings, newFindings } from '../knowledge/platform/mobileLayoutLint.js';
|
|
3
|
+
import { resolveProfile } from '../knowledge/platform/resolve.js';
|
|
4
|
+
import { fetchCatalogSlugs } from './components.js';
|
|
5
|
+
import { flowPath } from './screens.js';
|
|
6
|
+
/**
|
|
7
|
+
* Sayfa/layout/widget yazma yollarının profil kapısı: mobil profilde `MOB-*` bulgularını
|
|
8
|
+
* `layoutWarnings[]` satırı olarak döner. Web profilinde HİÇBİR istek atmaz ve boş dizi döner —
|
|
9
|
+
* web yanıtı profil öncesiyle aynı kalır (G1 golden).
|
|
10
|
+
*/
|
|
11
|
+
export async function screenProfileWarnings(platform, resolved, project, environment, applicationId, input) {
|
|
12
|
+
if (resolved.profile.platform === 'web')
|
|
13
|
+
return [];
|
|
14
|
+
const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId).catch(() => undefined);
|
|
15
|
+
// Tam kabuk ağacı yazan layout yolları (create/update/patch) mizanpaj iskeletini de denetler (MOB-015…018).
|
|
16
|
+
const layoutTree = resolved.profile.platform === 'mobile' && input.kind === 'layout' && input.nodes !== undefined
|
|
17
|
+
? mobileLayoutFindings({ kind: 'layout', tree: input.nodes })
|
|
18
|
+
: [];
|
|
19
|
+
return findingLines([...profileFindings(resolved.profile, { ...input, ...(catalog ? { catalog } : {}) }), ...layoutTree]);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Sayfa yazımının mizanpaj iskeleti (MOB-015…018): sayfanın kabuğu okunur, çünkü üst pay ve app-bar kabukla
|
|
23
|
+
* birlikte karar verilir. `before` verilirse (yama) yalnız yazımın getirdiği bulgular döner. Web profilinde
|
|
24
|
+
* istek atmaz; kabuk okunamazsa kabuğa bağlı kurallar koşmaz ve bilgi satırı döner.
|
|
25
|
+
*/
|
|
26
|
+
export async function pageLayoutWarnings(platform, resolved, project, environment, applicationId, input) {
|
|
27
|
+
if (resolved.profile.platform !== 'mobile')
|
|
28
|
+
return [];
|
|
29
|
+
const shell = await pageShell(platform, project, environment, applicationId, input.layoutId);
|
|
30
|
+
const after = mobileLayoutFindings({ kind: 'page', tree: input.tree, shell });
|
|
31
|
+
return findingLines(input.before === undefined ? after : newFindings(mobileLayoutFindings({ kind: 'page', tree: input.before, shell }), after));
|
|
32
|
+
}
|
|
33
|
+
/** Kabuk yamasının getirdiği iskelet bulguları (`layout_schema_patch`); web profilinde boş. */
|
|
34
|
+
export function layoutPatchWarnings(resolved, before, after) {
|
|
35
|
+
if (resolved.profile.platform !== 'mobile')
|
|
36
|
+
return [];
|
|
37
|
+
return findingLines(newFindings(mobileLayoutFindings({ kind: 'layout', tree: before }), mobileLayoutFindings({ kind: 'layout', tree: after })));
|
|
38
|
+
}
|
|
39
|
+
async function pageShell(platform, project, environment, applicationId, layoutId) {
|
|
40
|
+
if (typeof layoutId !== 'string' || layoutId === '')
|
|
41
|
+
return { kind: 'none' };
|
|
42
|
+
try {
|
|
43
|
+
const response = await platform.tenant('GET', project, `/layouts/${applicationId}/${layoutId}`, { environment });
|
|
44
|
+
return Array.isArray(response?.data?.views) ? describeShell(response.data.views) : { kind: 'unknown' };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { kind: 'unknown' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `set_event` ile bağlanan akışların adımları (MOB-005). Akış gövdeleri şemada değil ayrı uçtadır;
|
|
52
|
+
* okunamazsa bulgu üretilmez, yazım yine geçer.
|
|
53
|
+
*/
|
|
54
|
+
export async function boundFlowSteps(platform, resolved, project, environment, host, applicationId, hostId, ops) {
|
|
55
|
+
if (resolved.profile.platform === 'web')
|
|
56
|
+
return [];
|
|
57
|
+
const flowIds = new Set(ops.filter(entry => entry.op === 'set_event').map(entry => String(entry.eventValue ?? '')).filter(id => id.length > 0));
|
|
58
|
+
if (flowIds.size === 0)
|
|
59
|
+
return [];
|
|
60
|
+
try {
|
|
61
|
+
const hosted = await platform.tenant('GET', project, flowPath(host, applicationId, hostId), { environment });
|
|
62
|
+
return (hosted?.data ?? [])
|
|
63
|
+
.filter(flow => flowIds.has(String(flow.id ?? '')))
|
|
64
|
+
.flatMap(flow => [...(Array.isArray(flow.steps) ? flow.steps : []), ...(Array.isArray(flow.errorSteps) ? flow.errorSteps : [])]);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Akış yazma yollarının profil kapısı (MOB-005): mobil uygulamada `download`/`setcookie` adımının
|
|
72
|
+
* davranış farkını `platformWarnings`'e yazar. Web profilinde istek atmaz, boş döner.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Değişken yazma yollarının profil kapısı (MOB-014). Yazılan set saklama türü taşımıyorsa uygulama
|
|
76
|
+
* tipi ÇÖZÜLMEZ — ek istek atılmaz, yanıt profil öncesiyle aynı kalır. Tip çözülemediyse (uygulama
|
|
77
|
+
* okunamadı) bulgu üretilmez: gateway bilmediği platform için "uygulanmıyor" demez.
|
|
78
|
+
*/
|
|
79
|
+
export async function variableProfileWarnings(platform, applicationId, entries) {
|
|
80
|
+
if (!entries.some(entry => entry.storage !== undefined))
|
|
81
|
+
return [];
|
|
82
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
83
|
+
if (!resolved.known)
|
|
84
|
+
return [];
|
|
85
|
+
return findingLines(stateStorageFindings(resolved.profile, entries));
|
|
86
|
+
}
|
|
87
|
+
export async function flowProfileWarnings(platform, applicationId, steps) {
|
|
88
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
89
|
+
if (resolved.profile.platform === 'web')
|
|
90
|
+
return [];
|
|
91
|
+
return findingLines(profileFindings(resolved.profile, { kind: 'page', steps }));
|
|
92
|
+
}
|
package/dist/tools/projects.js
CHANGED
|
@@ -70,6 +70,32 @@ function rows(payload) {
|
|
|
70
70
|
const data = payload?.data;
|
|
71
71
|
return Array.isArray(data) ? data : [];
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* 2026-09-15'ten eski backend'lerde `GET /p/{slug}` (`GetOrganizationProjectBySlugQuery`)
|
|
75
|
+
* `OrganizationProjectDto.Map`'i yayın sürümü vermeden çağırıyordu: `organizationSchema.latestVersion` null
|
|
76
|
+
* gelir, prune onu düşürür ve "latestVersion yoksa yayınlı şema yok" okuması yanlış olurdu (TB-67, backend'de
|
|
77
|
+
* çözüldü). Slug yanıtı sürümü taşıyorsa olduğu gibi döner; taşımıyorsa (eski build) abonelik bloğu id ucundan
|
|
78
|
+
* (`GetOrganizationProjectQuery`) alınır. İd ucu düşerse (o uç `RequirePermission` taşımaz, Organization.API onu
|
|
79
|
+
* yalnız super-admin'e açar) slug yanıtı döner ve söylenir.
|
|
80
|
+
*/
|
|
81
|
+
async function withPublishedSchemaVersion(platform, payload) {
|
|
82
|
+
const data = payload?.data;
|
|
83
|
+
if (!data || typeof data.id !== 'string' || data.accessModel !== 'complex')
|
|
84
|
+
return payload;
|
|
85
|
+
const latestVersion = data.organizationSchema?.latestVersion;
|
|
86
|
+
if (latestVersion !== undefined && latestVersion !== null)
|
|
87
|
+
return payload;
|
|
88
|
+
try {
|
|
89
|
+
const byId = await platform.org('GET', `/projects/${data.id}`);
|
|
90
|
+
const organizationSchema = byId?.data?.organizationSchema;
|
|
91
|
+
return organizationSchema && typeof organizationSchema === 'object'
|
|
92
|
+
? { ...payload, data: { ...data, organizationSchema } }
|
|
93
|
+
: payload;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return withNormalization(payload, ["organizationSchema.latestVersion doğrulanamadı (slug ucu GET /p/{slug} sürüm taşımaz, GET /projects/{id} okunamadı): yokluğu 'yayınlı şema yok' demek DEĞİLDİR — organization_schema_get ile doğrulayın (TB-67)."]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
73
99
|
const applicationInput = z.object({
|
|
74
100
|
slug: z.string().min(1),
|
|
75
101
|
name: z.string().min(1),
|
|
@@ -124,32 +150,35 @@ export function registerProjectTools(server, platform) {
|
|
|
124
150
|
const config = platform.config;
|
|
125
151
|
server.registerTool('project_list', {
|
|
126
152
|
title: 'Projeleri listele',
|
|
127
|
-
description:
|
|
153
|
+
description: "Organizasyondaki projeleri arar ve sayfalı listeler. Her satır accessModel ('simple' | 'complex') ve organizationSchema aboneliğini taşır (alanların anlamı project_get açıklamasında).",
|
|
128
154
|
inputSchema: { searchTerm: z.string().optional(), ...pagingParams },
|
|
129
155
|
annotations: readOnly
|
|
130
156
|
}, handle(config, args => platform.org('GET', '/projects', { query: args })));
|
|
131
157
|
server.registerTool('project_get', {
|
|
132
158
|
title: 'Proje getir',
|
|
133
|
-
description: "Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır. Yanıttaki 'localization' bloğu gateway'in eklediği türetilmiş alandır: platform dil alanlarını null olduklarında serileştirmede düşürüyor, blok bunu görünür kılar ve runtime'ın çevirileri çözüp çözemeyeceğini söyler.",
|
|
159
|
+
description: "Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır (her ortamda enforcementMode: off | shadow | enforce). Yanıttaki 'localization' bloğu gateway'in eklediği türetilmiş alandır: platform dil alanlarını null olduklarında serileştirmede düşürüyor, blok bunu görünür kılar ve runtime'ın çevirileri çözüp çözemeyeceğini söyler. accessModel projenin türüdür; organizationSchema { receiveUpdates, appliedVersion, latestVersion, customizedAt } kompleks projenin şema aboneliğidir — gateway null alanları düşürür: customizedAt YOKSA proje özelleştirilmemiştir, appliedVersion yoksa şema hiç uygulanmamıştır, latestVersion yoksa organizasyonda yayınlı şema yoktur (platform_guide('getting-started') §'Basit mi kompleks mi'). slug ile çağrıda platformun slug ucu latestVersion'ı taşımaz; gateway organizationSchema bloğunu GET /projects/{id}'den alır, o okuma düşerse yanıtta gatewayNormalized notu olur ve latestVersion'ın yokluğu bir şey söylemez (TB-67).",
|
|
134
160
|
inputSchema: { id: guid.optional(), slug: z.string().optional().describe('id verilmediyse zorunlu') },
|
|
135
161
|
annotations: readOnly
|
|
136
162
|
}, handle(config, async (args) => {
|
|
137
163
|
if (!args.id && !args.slug) {
|
|
138
164
|
throw new Error("'id' ya da 'slug' parametrelerinden biri zorunlu.");
|
|
139
165
|
}
|
|
140
|
-
|
|
166
|
+
if (args.id)
|
|
167
|
+
return withLocalization(await platform.org('GET', `/projects/${args.id}`));
|
|
168
|
+
return withLocalization(await withPublishedSchemaVersion(platform, await platform.org('GET', `/p/${args.slug}`)));
|
|
141
169
|
}));
|
|
142
170
|
server.registerTool('project_create', {
|
|
143
171
|
title: 'Proje oluştur',
|
|
144
172
|
description: "Yeni proje oluşturur. templateOptions verilmezse 'application' zorunludur (ilk application projeyle birlikte açılır). LoaderImage bu tool'da desteklenmez. " + constraintSummary('CreateOrganizationProjectCommand') + ` Gateway POST'tan önce project_list ile slug'ı arar; doluysa istek gitmez ve 409 anlamlı mesajla döner. DİL: create ucu artık languages/defaultLanguage alanlarını kendisi taşır ve gövdeyle birlikte gider. Vermezseniz gateway varsayılan [${SEED_LANGUAGES.join(', ')}] + '${SEED_DEFAULT_LANGUAGE}' tohumunu koyar ve bunu gatewayNormalized ile bildirir — dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). templateOptions verildiğinde TOHUM atlanır (template kendi dil ayarını taşır); açıkça verdiğiniz diller o yolda da gönderilir.`,
|
|
145
173
|
inputSchema: {
|
|
146
174
|
name: z.string().min(1),
|
|
147
|
-
slug: z.string().min(1).describe(
|
|
175
|
+
slug: z.string().min(1).describe("Veritabanı adı zeyrek_<org>_<slug> ve rol adı bundan türer: 2–40 karakter, yalnız küçük harf ve rakam, parçalar arasında tek '-' ya da '_' ('__' yasak); organizasyonlar arası tekil (PRJ-005). Sonradan değiştirmek bütün runtime rotalarını kırar"),
|
|
148
176
|
theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni) — Studio tema panelinin yazdığı kayıtla aynı şekil: mode · palette · paletteDark · fontFamily · typeScale · shadow · presets · componentPresets. Verilmezse proje platformun varsayılan temasıyla (accent #0052CC, Inter 14px) doğar. Kurulmuş bir projenin temasını DEĞİŞTİRMEK için theme_update: kısmi günceller, şema/lint'ten geçirir ve hangi CSS değişkeninin değiştiğini söyler"),
|
|
149
177
|
languages: z.array(z.string().min(1)).min(1).optional().describe(`Proje dilleri (ISO 639-1; 'tr-TR' gibi bölgeli değer kabul edilir ve 'tr'ye indirgenir, indirgeme gatewayNormalized ile bildirilir). Verilmezse gateway [${SEED_LANGUAGES.join(', ')}] tohumlar — projenin dilsiz doğmasına ve çevirilerin sessizce çözülmemesine izin verilmez`),
|
|
150
178
|
defaultLanguage: z.string().min(1).optional().describe(`languages içinde olmalı (indirgenmiş hâliyle karşılaştırılır); yalnız defaultLanguage verirseniz languages onunla kurulur. İkisi de yoksa '${SEED_DEFAULT_LANGUAGE}'`),
|
|
151
179
|
application: applicationInput.optional(),
|
|
152
|
-
templateOptions: templateOptionsInput.optional().describe('Verilirse proje template\'ten kurulur (asenkron); sonucu project_list ile izleyin')
|
|
180
|
+
templateOptions: templateOptionsInput.optional().describe('Verilirse proje template\'ten kurulur (asenkron); sonucu project_list ile izleyin'),
|
|
181
|
+
accessModel: z.enum(['simple', 'complex']).optional().describe("Proje türü; varsayılan 'simple' (bugünkü davranış: kimlik ve yetkiyi uygulamanın kendi middleware'i kurar). 'complex': proje organizasyonun YAYINLI SON rol şemasının kopyasıyla doğar (yayın yoksa organization_units/organization_positions boştur — ORG-002), platform yönetimli uygulama oturumu ve karar motoru devrededir, yeni ortamların zorlama modu 'enforce'tur; şema tablolarına genel satır yazımı kapalıdır (ORG-001). templateOptions ile verilirse yok sayılır — template kaynak projenin türünü taşır")
|
|
153
182
|
},
|
|
154
183
|
annotations: writes
|
|
155
184
|
}, handle(config, async (args) => {
|
|
@@ -159,7 +188,7 @@ export function registerProjectTools(server, platform) {
|
|
|
159
188
|
if (!languages.codes.includes(defaultLanguage.code)) {
|
|
160
189
|
throw new PlatformApiError(`project_create: defaultLanguage '${defaultLanguage.code}' languages listesinde (${languages.codes.join(', ')}) yok — platform bunu 422 ile reddeder (PRJ-002). Kodu listeye ekleyin ya da listedeki bir kodu varsayılan yapın.`, 422);
|
|
161
190
|
}
|
|
162
|
-
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'create' }));
|
|
191
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'create' }), false);
|
|
163
192
|
// PRJ-001: slug çakışması 2026-09-08 öncesi build'de 500 'DatabaseNameAlreadyExistsException'
|
|
164
193
|
// dönüyordu (3. koşu S-1); arama ucu düşerse ön kontrol atlanır, karar platforma kalır.
|
|
165
194
|
const existing = rows(await platform.org('GET', '/projects', { query: { searchTerm: args.slug, pageLength: 200 } }).catch(() => undefined))
|
|
@@ -189,6 +218,14 @@ export function registerProjectTools(server, platform) {
|
|
|
189
218
|
form.set('TemplateOptions.TemplateReceiveUpdates', String(args.templateOptions.templateReceiveUpdates));
|
|
190
219
|
}
|
|
191
220
|
}
|
|
221
|
+
// Template yolunda platform kaynak projenin türünü kullanır ve istemcinin gönderdiğini yok
|
|
222
|
+
// sayar (05 §2); göndermek "complex istedim" yanılgısı üretirdi, gönderilmez ve söylenir.
|
|
223
|
+
if (args.accessModel && args.templateOptions) {
|
|
224
|
+
notes.push(`accessModel '${args.accessModel}' gönderilmedi: template yolunda proje türü template'in kaynak projesinden gelir. Sonucu project_get accessModel alanından doğrulayın.`);
|
|
225
|
+
}
|
|
226
|
+
else if (args.accessModel) {
|
|
227
|
+
form.set('AccessModel', args.accessModel);
|
|
228
|
+
}
|
|
192
229
|
// Dil artık create ucunun kendi alanı (CreateOrganizationProjectCommand.Languages /
|
|
193
230
|
// DefaultLanguage); ayrı bir PUT'a gerek yok. Template yolunda yalnız TOHUM atlanır: template
|
|
194
231
|
// kendi dil ayarını getiriyor ve uydurulmuş bir varsayılanla onu ezmek yanlış olur — çağıranın
|
|
@@ -203,7 +240,29 @@ export function registerProjectTools(server, platform) {
|
|
|
203
240
|
notes.push(`Proje dilleri gateway tarafından varsayılanla kuruldu: languages [${languages.codes.join(', ')}], defaultLanguage '${defaultLanguage.code}'. Dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). Başka dil istiyorsanız project_update ile değiştirin.`);
|
|
204
241
|
}
|
|
205
242
|
}
|
|
206
|
-
|
|
243
|
+
const created = await platform.org('POST', '/projects', { form });
|
|
244
|
+
const schema = (created?.data ?? {});
|
|
245
|
+
if (schema.accessModel === 'complex') {
|
|
246
|
+
// Oluşturma yanıtı 2026-09-15'ten beri sürümü taşır (TB-67 backend'de çözüldü); daha eski build'lerde
|
|
247
|
+
// OrganizationProjectDto.Map sürümsüz çağrılıp alan null gelir — "yayın yok" demeden önce id ucu bir kez okunur.
|
|
248
|
+
let latestVersion = schema.organizationSchema?.latestVersion;
|
|
249
|
+
let versionUnknown = false;
|
|
250
|
+
if ((latestVersion === undefined || latestVersion === null) && typeof schema.id === 'string') {
|
|
251
|
+
try {
|
|
252
|
+
const byId = await platform.org('GET', `/projects/${schema.id}`);
|
|
253
|
+
latestVersion = byId?.data?.organizationSchema?.latestVersion;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
versionUnknown = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
notes.push(versionUnknown
|
|
260
|
+
? "Kompleks proje açıldı; organizasyonun yayınlı şema sürümü doğrulanamadı (oluşturma yanıtında latestVersion yok, GET /projects/{id} okunamadı). Organizasyon tablolarının dolu doğup doğmadığını organization_schema_get ve row_list('organization_units') ile doğrulayın (ORG-002, TB-67)."
|
|
261
|
+
: latestVersion === undefined || latestVersion === null
|
|
262
|
+
? "Kompleks proje açıldı ama organizasyonda YAYINLI şema yok: organization_units / organization_positions tabloları boş doğdu (ORG-002). Şema yönetim konsolunda yayınlanınca abone projeye senkronla gelir; durumu organization_schema_get ve project_get organizationSchema ile izleyin."
|
|
263
|
+
: `Kompleks proje organizasyon şemasının ${String(latestVersion)}. sürümüne abone doğdu; kopya her ortama senkronla yazılır (asenkron — project_get organizationSchema.appliedVersion dolunca tamamdır). Şema tablolarına row_* yazımı kapalıdır (ORG-001); ağacı row_list ile okuyun.`);
|
|
264
|
+
}
|
|
265
|
+
return attachFindings(withNormalization(created, notes), findings);
|
|
207
266
|
}));
|
|
208
267
|
server.registerTool('project_update', {
|
|
209
268
|
title: 'Proje güncelle',
|
|
@@ -214,11 +273,12 @@ export function registerProjectTools(server, platform) {
|
|
|
214
273
|
slug: z.string().optional(),
|
|
215
274
|
theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni); verilen kayıt mevcut temanın YERİNE geçer — bir alanı değiştirmek için theme_update kullanın (kısmi birleştirir, lint'ten geçirir). VERİLMEZSE mevcut tema korunur — gateway projeden okuyup gövdeye geri koyar, çünkü platform Theme alanı gövdede yokken temayı siler ve silinen tema bütün ortamların runtime'ına iner (S-1). Temayı gerçekten temizlemek için theme: {} verin"),
|
|
216
275
|
languages: z.array(languageCode).min(1).optional().describe("ISO 639-1 iki harfli kod listesi (proje dilleri, platformun 184 kodluk sözlüğünden — PRJ-002). Runtime çevirileri YALNIZ bu kodlarla çözer; liste kurulmadan yazılan çeviriler 200 döner ama ekrana ham anahtar basılır (S-15). Yeni proje bu liste BOŞ doğar — çeviri kullanacaksanız burayı kurmak ilk adımdır"),
|
|
217
|
-
defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir")
|
|
276
|
+
defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir"),
|
|
277
|
+
revealReasonMinLength: z.number().int().min(0).max(1000).optional().describe("Gerekçeli açmada (access_reveal) kabul edilen en kısa gerekçe; 0 kuralı kapatır. VERİLMEZSE mevcut değer korunur. Runtime reveal yolunda (access_reveal) yalnız kompleks projede etkilidir, Studio ızgara reveal'inde HER projede zorlanır; daha kısa gerekçe 400 RevealReasonTooShort alır ve denetim defterine REVEAL-DENIED düşer (TB-98)")
|
|
218
278
|
},
|
|
219
279
|
annotations: updates
|
|
220
280
|
}, handle(config, async (args) => {
|
|
221
|
-
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }));
|
|
281
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }), false);
|
|
222
282
|
// Çağıranın hatası okumadan ÖNCE döner: bozuk tema metni için projeyi çekmenin anlamı yok.
|
|
223
283
|
const requestedTheme = themeField(args.theme, 'project_update');
|
|
224
284
|
// Platform gövdede slug yoksa onu ADDAN yeniden üretiyor. Yalnız dil ayarını değiştiren bir
|
|
@@ -242,6 +302,8 @@ export function registerProjectTools(server, platform) {
|
|
|
242
302
|
languages?.codes.forEach(code => form.append('Languages', code));
|
|
243
303
|
if (defaultLanguage)
|
|
244
304
|
form.set('DefaultLanguage', defaultLanguage.code);
|
|
305
|
+
if (args.revealReasonMinLength !== undefined)
|
|
306
|
+
form.set('RevealReasonMinLength', String(args.revealReasonMinLength));
|
|
245
307
|
const payload = await platform.org('PUT', `/projects/${args.id}`, { form });
|
|
246
308
|
const notes = [
|
|
247
309
|
...(languages?.notes ?? []),
|
package/dist/tools/rows.js
CHANGED
|
@@ -3,16 +3,19 @@ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environme
|
|
|
3
3
|
import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
4
4
|
import { withNormalization } from '../knowledge/normalize.js';
|
|
5
5
|
import { RefResolver } from '../platform/refs.js';
|
|
6
|
-
import { attachFindings, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
7
|
-
import {
|
|
6
|
+
import { attachFindings, applyPolicyMode, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
7
|
+
import { protectedFilterFindings, systemEntityWriteFindings, systemManagedEntity } from '../knowledge/access.js';
|
|
8
|
+
import { filterOperatorError, policyRules } from '../knowledge/rules.js';
|
|
8
9
|
import { languageHeader } from '../knowledge/languageCodes.js';
|
|
9
10
|
import { handle } from '../platform/responses.js';
|
|
10
11
|
import { uploadFile } from './files.js';
|
|
11
12
|
import { PlatformApiError } from '../platform/errors.js';
|
|
12
13
|
import { secretKeyPattern } from '../platform/secrets.js';
|
|
13
14
|
import { csvRows } from '../platform/csv.js';
|
|
15
|
+
import { rowFilterSchema } from '../knowledge/rowFilter.js';
|
|
14
16
|
import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
15
17
|
import { extname, resolve } from 'node:path';
|
|
18
|
+
import { jsonArray } from '../platform/jsonArray.js';
|
|
16
19
|
import { z } from 'zod';
|
|
17
20
|
const rowValues = z.record(z.unknown()).describe("Alan SLUG'ı → değer sözlüğü; anahtarlar entity_get çıktısındaki field SLUG'larıdır (name değil — name kullanmak 'Please enter a valid value' hatası verir)");
|
|
18
21
|
/**
|
|
@@ -35,6 +38,17 @@ function derivedRowFindings(rows) {
|
|
|
35
38
|
guide: 'ngapps://guides/entity'
|
|
36
39
|
}];
|
|
37
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* ORG-001'in çözüm sonrası yarısı. Backend yalnız entity id'sine bakar (`SystemManagedEntityIds`); ön
|
|
43
|
+
* denetim GUID'i, öneksiz slug'ı ve yerleşik datasource önekini istek gitmeden tanır, başka adla yazılmış
|
|
44
|
+
* önek ise ancak çözülen id'yle anlaşılır. Dış datasource'taki aynı adlı tablo (`ik/user_positions`) başka
|
|
45
|
+
* bir id'ye çözüldüğü için geçer. Ön denetim zaten eşleştiyse bulgu ikinci kez yazılmaz.
|
|
46
|
+
*/
|
|
47
|
+
function resolvedSystemEntityFindings(config, entityId, resolvedEntityId, operation) {
|
|
48
|
+
if (config.policyMode === 'off' || systemManagedEntity(entityId))
|
|
49
|
+
return [];
|
|
50
|
+
return enforcePolicy(applyPolicyMode(config, systemEntityWriteFindings(resolvedEntityId, operation)), false);
|
|
51
|
+
}
|
|
38
52
|
const bulkBatchSize = 1000;
|
|
39
53
|
const maxPathBytes = 50 * 1024 * 1024;
|
|
40
54
|
const maxPathRows = 100_000;
|
|
@@ -157,8 +171,8 @@ const templateFormatNotes = {
|
|
|
157
171
|
csv: 'csv şablonda ipucu satırı ve `#zeyrek` satırı yoktur; başlıklar alan adıdır.',
|
|
158
172
|
json: 'JSON şablon `{ meta, data }` gövdesidir; `meta` analizde atlanır, satırlar `data`dadır ve anahtarları alan slug\'ıdır.'
|
|
159
173
|
};
|
|
160
|
-
const rowFiltersParam = z.array(
|
|
161
|
-
.describe(
|
|
174
|
+
const rowFiltersParam = jsonArray(z.array(rowFilterSchema())).optional()
|
|
175
|
+
.describe("Platform Filter dizisi: tekil { filterType: 'single', field, operator, value } ya da grup { filterType: 'group', conditionType: 'and'|'or', children: [...] }; işleçler platform enum'undan (ngapps://schemas/Filter)");
|
|
162
176
|
const exportScopeParam = z.object({
|
|
163
177
|
kind: z.enum(['view', 'all', 'selected']).describe("'view' filtre + sıralama ile, 'all' tüm satırlar, 'selected' rowIds listesi"),
|
|
164
178
|
filters: rowFiltersParam,
|
|
@@ -216,7 +230,7 @@ export function registerRowTools(server, platform) {
|
|
|
216
230
|
project: projectParam,
|
|
217
231
|
environment: environmentParam,
|
|
218
232
|
entityId: entityRef,
|
|
219
|
-
selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır'),
|
|
233
|
+
selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır. Bilinmeyen alan adı platformda 400 UnknownField döner (id ve sys_* bilinen sayılır) — sessizce düşmez.'),
|
|
220
234
|
filters: rowFiltersParam,
|
|
221
235
|
language: languageParam,
|
|
222
236
|
includeTranslations: includeTranslationsParam,
|
|
@@ -224,26 +238,32 @@ export function registerRowTools(server, platform) {
|
|
|
224
238
|
},
|
|
225
239
|
annotations: readOnly
|
|
226
240
|
}, handle(config, async ({ project, environment, entityId, language, ...body }) => {
|
|
241
|
+
const operatorError = filterOperatorError('row_list', body.filters);
|
|
242
|
+
if (operatorError)
|
|
243
|
+
throw new PlatformApiError(operatorError, 400);
|
|
227
244
|
const notes = [];
|
|
228
245
|
const headers = languageHeader(language, notes);
|
|
229
246
|
const resolver = new RefResolver(platform, project, environment);
|
|
230
247
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
248
|
+
// PROT-001 / PROT-002: korumalı alanda metin araması ve sıralama platformda 400'dür; entity
|
|
249
|
+
// yalnız riskli bir işleç ya da sıralama varsa okunur.
|
|
250
|
+
const protectionFindings = enforcePolicy(applyPolicyMode(config, await protectedFilterFindings(platform, resolver, entityId, body)), false);
|
|
231
251
|
const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/list`, { environment, body, headers });
|
|
232
252
|
const wanted = body.selectedFields ?? [];
|
|
233
253
|
const rows = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.data : undefined;
|
|
234
254
|
if (!wanted.length || !Array.isArray(rows))
|
|
235
|
-
return withNormalization(withResolved(payload, resolver.resolved), notes);
|
|
255
|
+
return attachFindings(withNormalization(withResolved(payload, resolver.resolved), notes), protectionFindings);
|
|
236
256
|
/**
|
|
237
257
|
* 'localizations' keep listesine girmezse includeTranslations'ın getirdiği çeviriler tam da
|
|
238
258
|
* onları isteyen çağrıda kırpılırdı: iki parametre birlikte verildiğinde sessizce boşa çıkar.
|
|
239
259
|
*/
|
|
240
260
|
const keep = new Set([...wanted.map(field => field.toLowerCase()), 'id', ...(body.includeTranslations ? ['localizations'] : [])]);
|
|
241
|
-
return withNormalization(withResolved({
|
|
261
|
+
return attachFindings(withNormalization(withResolved({
|
|
242
262
|
...payload,
|
|
243
263
|
data: rows.map(row => row && typeof row === 'object' && !Array.isArray(row)
|
|
244
264
|
? Object.fromEntries(Object.entries(row).filter(([key]) => keep.has(key.toLowerCase())))
|
|
245
265
|
: row)
|
|
246
|
-
}, resolver.resolved), notes);
|
|
266
|
+
}, resolver.resolved), notes), protectionFindings);
|
|
247
267
|
}));
|
|
248
268
|
server.registerTool('row_get', {
|
|
249
269
|
title: 'Satır getir',
|
|
@@ -286,17 +306,17 @@ export function registerRowTools(server, platform) {
|
|
|
286
306
|
const wantedLocalizations = localizations && Object.keys(localizations).length > 0 ? localizations : undefined;
|
|
287
307
|
const fileRows = path ? await readRowsFromPath('row_create', path) : undefined;
|
|
288
308
|
const bulkRows = rows ?? fileRows;
|
|
289
|
-
const
|
|
309
|
+
const inputFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId, ...(bulkRows ? { rows: bulkRows } : { row }) }, { operation: 'create' }), false);
|
|
290
310
|
const resolver = new RefResolver(platform, project, environment);
|
|
311
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
312
|
+
const shapeFindings = [...inputFindings, ...resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'create')];
|
|
291
313
|
if (bulkRows) {
|
|
292
|
-
const bulkEntityId = await resolveEntityId(resolver, entityId);
|
|
293
314
|
const bulkDrift = unmappedMembers('CreateBulkRowCommand', { rows: bulkRows, ...(rowContext ? { rowContext } : {}), ...(relationsByName ? { relationsByName } : {}) });
|
|
294
|
-
const outcome = await writeInBatches(platform, 'POST', project, environment, headers,
|
|
315
|
+
const outcome = await writeInBatches(platform, 'POST', project, environment, headers, resolvedEntityId, bulkRows, rowContext, relationsByName);
|
|
295
316
|
if (fileRows)
|
|
296
317
|
notes.push(`${path} okundu: ${fileRows.length} satır, ${Math.ceil(fileRows.length / bulkBatchSize)} parti.`);
|
|
297
318
|
return attachFindings(withUnmappedMembers(withNormalization(withResolved(outcome, resolver.resolved), notes), bulkDrift), [...shapeFindings, ...(fileRows ? [] : derivedRowFindings(bulkRows))]);
|
|
298
319
|
}
|
|
299
|
-
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
300
320
|
const body = { row, ...(wantedLocalizations ? { localizations: wantedLocalizations } : {}), ...(rowContext ? { rowContext } : {}) };
|
|
301
321
|
const drift = unmappedMembers('CreateRowCommand', body);
|
|
302
322
|
const response = await platform.tenantWithHeaders('POST', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body });
|
|
@@ -354,10 +374,11 @@ export function registerRowTools(server, platform) {
|
|
|
354
374
|
const notes = [];
|
|
355
375
|
const fileRows = path ? await readRowsFromPath('row_update', path) : undefined;
|
|
356
376
|
const bulkRows = rows ?? fileRows;
|
|
357
|
-
const
|
|
377
|
+
const inputFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId, ...(bulkRows ? { rows: bulkRows } : { row }) }, { operation: 'update' }), false);
|
|
358
378
|
const headers = languageHeader(language, notes);
|
|
359
379
|
const resolver = new RefResolver(platform, project, environment);
|
|
360
380
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
381
|
+
const shapeFindings = [...inputFindings, ...resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'update')];
|
|
361
382
|
if (bulkRows) {
|
|
362
383
|
const bulkDrift = unmappedMembers('UpdateBulkRowCommand', { rows: bulkRows, ...(rowContext ? { rowContext } : {}), ...(relationsByName ? { relationsByName } : {}) });
|
|
363
384
|
const outcome = await writeInBatches(platform, 'PUT', project, environment, headers, resolvedEntityId, bulkRows, rowContext, relationsByName);
|
|
@@ -369,6 +390,36 @@ export function registerRowTools(server, platform) {
|
|
|
369
390
|
const drift = unmappedMembers('UpdateRowCommand', body);
|
|
370
391
|
return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body }), resolver.resolved), notes), drift), shapeFindings);
|
|
371
392
|
}));
|
|
393
|
+
server.registerTool('row_reassign', {
|
|
394
|
+
title: 'Satırların sahibini değiştir',
|
|
395
|
+
description: "Satırların SAHİPLİK alanlarını (sys_owner_unit_id · sys_owner_user_id) değiştirir (POST /{project}/entities/{entityId}/row/reassign). Bu alanlar row_update ile YAZILMAZ: gövdede görülürse istek 400 OwnershipFieldNotWritable alır (eskiden sessizce düşüyordu, S2-7). Hedef satırlar 'rowIds' ile ya da 'filter' ile seçilir; en az biri zorunlu. En az bir hedef sahip verilmelidir: ownerUnitId birime, ownerUserId kişiye devreder — yalnız ownerUserId verilirse birim de o kişinin aktif birincil pozisyonundan türetilir (pozisyonu yoksa satırın birimi olduğu gibi kalır). Her satıra REASSIGN denetim kaydı düşer (eski sahip → yeni sahip). KİMLİK: bu uç öteki row_* araçları gibi bir STUDIO ucudur ve gateway'in platform kimliğiyle koşar — uygulama jetonu (asUser) burada geçmez, 401 alır. Uygulama KULLANICISININ kapsamıyla devretmek (ve 'entity.reassign' kapsamını gerçekten ölçmek) için aynı işi yapan sistem operasyonunu kullanın: systemOperation 'entity.row.reassign' olan bir uç açıp microservice_execute { asUser } ile çağırın. Kapsam kuralı orada işler: çağıran yalnız 'entity.reassign' grant'inin satır kapsamındaki satırları devredebilir; kapsamı unit ya da unit-tree olan çağıran için SAHİPSİZ satırlar (sys_owner_unit_id NULL) da kapsam içi sayılır — şubesi kapanan kayıtlara başka türlü kimse ulaşamazdı (06 §A).",
|
|
396
|
+
inputSchema: {
|
|
397
|
+
project: projectParam,
|
|
398
|
+
environment: environmentParam,
|
|
399
|
+
entityId: entityRef,
|
|
400
|
+
rowIds: z.array(z.string().min(1)).optional().describe("Devredilecek satırların birincil anahtar değerleri; 'filter' ile birlikte verilmez"),
|
|
401
|
+
filter: rowFiltersParam.describe("Devredilecek satırları seçen Platform Filter dizisi (row_list ile aynı biçim); 'rowIds' ile birlikte verilmez"),
|
|
402
|
+
ownerUnitId: guid.optional().describe("Yeni sahip birim (organization_tree_get); verilmezse ownerUserId'nin birimi yazılır"),
|
|
403
|
+
ownerUserId: guid.optional().describe('Yeni sahip kullanıcı (user_list); verilmezse satırın kullanıcı sahibi olduğu gibi kalır')
|
|
404
|
+
},
|
|
405
|
+
annotations: updates
|
|
406
|
+
}, handle(config, async ({ project, environment, entityId, rowIds, filter, ownerUnitId, ownerUserId }) => {
|
|
407
|
+
if (rowIds?.length && filter?.length)
|
|
408
|
+
throw new Error("row_reassign: 'rowIds' ve 'filter' birlikte verilmez — satırları ya id listesiyle ya filtreyle seçin.");
|
|
409
|
+
if (!rowIds?.length && !filter?.length)
|
|
410
|
+
throw new Error("row_reassign: hedef satır seçilmedi — 'rowIds' ya da 'filter' verin.");
|
|
411
|
+
if (!ownerUnitId && !ownerUserId)
|
|
412
|
+
throw new Error("row_reassign: hedef sahip verilmedi — 'ownerUnitId' ve/veya 'ownerUserId' verin.");
|
|
413
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
414
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
415
|
+
const body = {
|
|
416
|
+
...(rowIds?.length ? { rowIds } : {}),
|
|
417
|
+
...(filter?.length ? { filter } : {}),
|
|
418
|
+
...(ownerUnitId ? { ownerUnitId } : {}),
|
|
419
|
+
...(ownerUserId ? { ownerUserId } : {})
|
|
420
|
+
};
|
|
421
|
+
return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/reassign`, { environment, body }), resolver.resolved);
|
|
422
|
+
}));
|
|
372
423
|
server.registerTool('row_export', {
|
|
373
424
|
title: 'Satırları dışa aktar',
|
|
374
425
|
description: "Bir tablonun satırlarını xlsx / csv / json dosyasına yazar ve dosyayı projenin SÜRÜCÜSÜNE ('Dışa aktarmalar' klasörü) koyar; yanıt yalnız fileId ve sayılardır — dosya içeriği ajanın bağlamına HİÇ girmez, içeriği görmek için file_download kullanın. Kapsam üç şekildedir: 'view' (filtre + sıralama), 'all', 'selected' (rowIds). 5.000 satırın üstünde platform işi arka plana alır ve 202 ile jobId döner; import_status { jobId } ile izlenir, biten işin dosyası result.reportFileId'dedir. CSV UTF-8 BOM'lu yazılır; ayraç ve ondalık bir yerel ayar profilinden gelir (tr-TR `;` · `1,5`, en-US `,` · `1.5`): dışa aktarmada projenin dili seçer, şablonda 'locale' ile seçilir. 'reimportable: true' dosyaya id sütununu ve _zeyrek meta sayfasını ekler: dosya düzenlenip import_analyze → import_plan { mode: 'upsert', keyField: 'id' } ile geri yüklenebilir (Akış C). 'template: true' tablo satırı yazmaz: tablonun alanlarından İKİ SENTETİK örnek satırlı bir şablon üretir ve değiştirilmeden geri yüklenebilir. 'template: true, inline: true' şablonun şeklini (sütunlar, anahtar önerisi, iki satır) yanıta da koyar — toplu yazımdan önce dosyanın nasıl görünmesi gerektiğini indirmeden görmenin yolu budur; satır satır row_create yerine şablon başlıklarıyla CSV → file_upload { text } → import_analyze → import_plan → import_run, reçete platform_guide('entity') §'Dışa aktarma ve geri yükleme'.",
|
|
@@ -400,6 +451,9 @@ export function registerRowTools(server, platform) {
|
|
|
400
451
|
if ((scope?.rowIds?.length ?? 0) > maxSelectedRowIds) {
|
|
401
452
|
throw new PlatformApiError(`row_export: ${scope?.rowIds?.length} satır id'si, ${maxSelectedRowIds} sınırının üstünde. Bu kadar satırı seçerek değil filtreyle verin: scope { kind: 'view', filters: [...] }.`);
|
|
402
453
|
}
|
|
454
|
+
const operatorError = filterOperatorError('row_export', scope?.filters);
|
|
455
|
+
if (operatorError)
|
|
456
|
+
throw new PlatformApiError(operatorError, 400);
|
|
403
457
|
const resolvedFormat = format ?? 'xlsx';
|
|
404
458
|
const notes = [];
|
|
405
459
|
const resolver = new RefResolver(platform, project, environment);
|
|
@@ -521,8 +575,10 @@ export function registerRowTools(server, platform) {
|
|
|
521
575
|
throw new Error("'id' ve 'ids' birlikte verilemez.");
|
|
522
576
|
if (!id && !ids)
|
|
523
577
|
throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
|
|
578
|
+
enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId }, { operation: 'delete' }), false);
|
|
524
579
|
const resolver = new RefResolver(platform, project, environment);
|
|
525
580
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
581
|
+
resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'delete');
|
|
526
582
|
if (ids)
|
|
527
583
|
return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/bulk/delete`, { environment, body: { rowIds: ids } }), resolver.resolved);
|
|
528
584
|
return withResolved(await platform.tenant('DELETE', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(String(id))}`, { environment }), resolver.resolved);
|