# `one.manifest.json`: alan başvurusu

> Her Devoplus One uygulamasının kökündeki beyan dosyası. **Sözleşme platformun manifest şemasıdır** (platform
> deposunda `packages/manifest/src/schema.ts`, zod şeması + `validateManifest`). Bu belge o şemadan çıkarılmıştır; ikisi
> çelişirse şema esastır.

Doğrulamayı `one-ci validate <uygulama dizini>` yapar (`validateManifest` + giriş dosyasının varlığı); geçmeyen sürüm
yüklenmez. İskelet üretici (`create-devoplus-one-app`) ürettiği manifest'i aynı doğrulayıcıdan geçirir.

## Alanlar

```
one.manifest.json
├── schemaVersion        1 (sabit)
├── app
│   ├── slug             küçük harf/rakam, tek tire, harfle başlar, ≤ 24 (SLUG_RE, SLUG_MAX)
│   ├── name             { tr: 1-120 karakter (kırpılır), en?: 1-120 }
│   ├── version          x.y.z (önek/etiket yok)
│   ├── description?     { tr, en? }; name ile aynı kural
│   └── owner.email      e-posta (uydurulmaz)
├── entry                uygulamanın giriş dosyası, ^[\w./-]+\.(ts|js|mjs)$
├── scopes               string[] ≤ 32, varsayılan []; her öğe <modül>.<read|write|manage|execute> ya da ai.use (SCOPE_RE)
├── services             varsayılan {}; alt alanların varsayılanları uygulanır
│   ├── database         bool, varsayılan true;  kurulum başına veritabanı (`env.DB`)
│   ├── files            bool, varsayılan false; kurulum başına dosya deposu (`env.FILES`)
│   ├── cache            bool, varsayılan false; kurulum başına önbellek (`env.CACHE`)
│   ├── (eski adlar)     KALDIRILDI; yazılırsa yeni adını söyleyen hatayla reddedilir
│   ├── email, sms, push, orgSchema   bool, varsayılan false; yalnız beyandır, one-ci bunları bir kaynağa çevirmez
│   └── dexa?            { queryKeys?: string[], commandKeys?: string[] }; en az biri, aşağıda
├── menu
│   ├── title            { tr, en? }
│   ├── icon?            ≤ 64; PrimeIcons sınıfı (`pi pi-box`), aşağıda
│   └── group?           ≤ 64
├── permission
│   ├── code             One.<Ad>.<Ad> (PERMISSION_CODE_RE)
│   └── name             { tr, en? }
├── permissions          [{ code, name: { tr, en? } }]; uygulama içi ek izinler, varsayılan [], aşağıda
├── navigation           [{ path, label: { tr, en? }, permission? }] ≤ 12, varsayılan []; Portal'ın çizdiği sekmeler (sekme izniyle), aşağıda
├── actingSessions       "read" | "write", varsayılan "read"; Vekalet / Yerine Görüntüle oturumunda yazma
├── devicePermissions    ("camera" | "microphone" | "geolocation")[], tekil, varsayılan []; Permissions-Policy
├── agent?               Devoplus yapay zekasının çağırabileceği araçlar, `strict`, aşağıda
│   ├── summary          { tr: 1-1000, en? }; uygulamanın yapay zekaya kısa tanıtımı
│   └── tools            ≤ 20, varsayılan []; [{ name, title, description, readOnly, permission?, input }]
├── exports?             [{ name, version, description, permission?, request, response }]; uygulamalar arası okuma, veren taraf, aşağıda
├── imports?             [{ app, export, version, reason }]; uygulamalar arası okuma, alan taraf, aşağıda
├── widgets?             [{ name, version, title, kind, size, permission?, refreshSeconds?, params?, path? }]; Portal pano widget'ları, aşağıda
├── jobs?                zamanlanmış görev ve kuyruk, `strict`; en az bir zamanlama ya da kuyruk, aşağıda
│   ├── schedules        [{ name, cron, timeZone?, description? }] ≤ 10, varsayılan []
│   ├── queues           [{ name, maxRetries?, description? }] ≤ 10, varsayılan []
│   └── services?        arka plan işinin uygulama kimliğiyle çağıracağı servisler, aşağıda
├── sdkVersion?          uygulamanın derlendiği SDK sürümü (SemVer, ön sürüm eki dahil); ELLE YAZILMAZ, aşağıda
├── limits?              dispatch'in çağrı başına sınırı; platform tavanını aşamaz
│   ├── cpuMs?           tam sayı 1-1000
│   └── subRequests?     tam sayı 1-200
└── observability?       Event Logger kalıcılığı, `strict`
    └── headSamplingRate?  0 < oran ≤ 1, varsayılan 1 (her istek). Olay günlüğü platform tarafından HER ZAMAN açılır;
                           kapatmak bilerek mümkün değildir (`enabled` gibi bilinmeyen alan reddedilir)
```

Kök nesne **katıdır** (`strict`): bilinmeyen bir kök alan reddedilir, uydurulmuş bir alan sessizce yutulmaz.

## `scopes`

Biçim devoplus-auth scope kataloğuyla aynıdır; modül adının katalogda olup olmadığını manifest de `one-ci` de
denetlemez; o kapı devoplus-auth client kaydındadır ve belirteç `istemci kaydı ∩ oturum` scope'unu taşır. Manifest
`scopes` beyan ve biçim denetimi içindir, çalışma anında okunmaz. Kullanıcı kimliği (`sub`) scope'suz gelir.

`ai.use`: Devoplus yapay zekasını kullanma (SDK `completeAi`, uç `One/services/ai`). Dört eylem
kalıbının dışındaki TEK addır; `SCOPE_RE` onu ayrıca tanır (genel bir `.use` eylemi açılmaz, `hr.use` reddedilir).
Kullanıcının süreli onayına bağlı DEĞİLDİR.

## `agent`: Devoplus yapay zekasının araçları

Devoplus yapay zekası (Devoplus ajanı) uygulamanın verisine ve işlemlerine yalnız burada beyan edilen araçlarla,
kullanıcının kimliğiyle ulaşır: Devoplus kurulumu, uygulama iznini ve aracın beyanını doğrular, platform kapısına imzalı
çağrı yapar, kapı isteği uygulamaya `POST /__one/agent/<ad>` olarak verir; SDK `handleAgentRequest` karşılar. Araç
beyan eden uygulama `AGENT_GUIDE.md` taşımak ZORUNDADIR (aşağıda).

| Kural | Değer | Hata yolu |
|---|---|---|
| Ad | `^[a-z][a-z0-9-]{1,39}$` (`AGENT_TOOL_NAME_RE`; SDK ve dispatch ile birebir), tekil | `agent.tools.<i>.name` |
| Adet | en çok 20 (`AGENT_TOOLS_MAX`) | `agent.tools` |
| `title`, `description` | `{ tr, en? }`; başlık 1-200, açıklama 1-1000 karakter | öğenin yolu |
| `readOnly` | zorunlu boolean; `false` = değiştirir; yapay zeka kullanıcıdan onay ister, Vekalet / Yerine Görüntüle oturumunda kapalı | `agent.tools.<i>.readOnly` |
| `permission` | isteğe bağlı; uygulamanın ad alanında VE manifest'te beyanlı (`permission.code` ya da `permissions[]`) | `agent.tools.<i>.permission` |
| `input` | JSON Schema ALT KÜMESİ (aşağıda), JSON hali ≤ 8 KiB, derinlik ≤ 8 | `agent.tools.<i>.input.<şema yolu>` |
| Öğe ve bölüm | `strict` (bilinmeyen alan reddedilir) | öğenin yolu |

`input` alt kümesi (`AGENT_INPUT_KEYWORDS`): `type`, `properties`, `required`, `enum`, `minimum`, `maximum`,
`minLength`, `maxLength`, `items` ve `description`. Kök `"type": "object"` olmak zorundadır; her düğümde `type` yazılır
(`object`, `string`, `number`, `integer`, `boolean`, `array`; tip dizisi yok); anahtar-tip uyumu denetlenir
(`minLength` yalnız metinde, `minimum` yalnız sayıda, `items` yalnız dizide ve orada zorunlu); `required` yalnız
tanımlı alanları sayar; alan adı `^[A-Za-z_][A-Za-z0-9_]{0,63}$`, nesnede ≤ 50 alan; `enum` 1-100 tekil öğe ve düğümün
tipinde; `description` ≤ 500. Alt küme dışı anahtar (`pattern`, `format`, `$ref`, `oneOf`, `additionalProperties` vb.)
REDDEDİLİR. Sessizce yok sayılsaydı geliştirici denetlendiğini sanırdı. ⚠️ Nesneler çalışma anında **kapalıdır**: şemada
olmayan alan SDK'da reddedilir (`additionalProperties: false` örtük).

**Ajan rehberi:** `agent.tools` boş değilse `AGENT_GUIDE.md` zorunludur: `one-ci validate|plan|deploy` dosyayı, dört
başlığı (`## Uygulama ne yapar`, `## Veri`, `## Araçlar`, `## Örnek istekler`), her araç adının rehberde geçtiğini,
en az 400 karakteri ve 256 KiB tavanını denetler; eksikse yayın DURUR. `agent` yoksa ya da araç listesi boşsa yayın
durmaz, "Devoplus yapay zekası bu uygulamaya erişemez" uyarısı yazılır. SDK manifest bölümünü
`/.well-known/one-agent.json`, rehberi `/.well-known/one-agent.md` adresinden sunar (`agentManifestResponse`).

## `exports` ve `imports`: uygulamalar arası okuma

Bir uygulama verisinin bir bölümünü adlandırılmış ve sürümlü **okuma** uçlarıyla başka uygulamalara açabilir (`exports`);
başka bir uygulama bu uçlardan birine ihtiyacını gerekçesiyle beyan eder (`imports`). Paylaşım yalnız **aynı portalda**,
alan uygulamanın kurulumunda şirket yöneticisinin onayıyla ve yalnız okuma olarak çalışır.

⚠️ **Durum:** beyan, doğrulama, veren tarafın işleyicisi (SDK `defineExports` ve `handleExportRequest`, U5-2), alan
uygulamanın çağrısı (SDK `readAppExport`) ve dispatch ile çıkış katmanının çağrı yolu (U5-3) bu depodadır. Platformun öteki
tarafı (backend servis ucu, onay kaydı, aşağıdaki yayın kuralları ve Devoplus Console onay ekranı; U5-4) yazılıyor. O
tarafı yayına alınana kadar bu alanlar hiçbir çağrı açmaz.

| Kural | Değer | Hata yolu |
|---|---|---|
| `exports[].name` | `^[a-z][a-z0-9-]{0,39}$` (`EXPORT_NAME_RE`); adres yoluna kodlanmadan yazılır (`/__one/exports/<name>/v<version>`) | `exports.<i>.name` |
| `exports[].version` | tam sayı, 1-999999 (`1.0` gibi bir yazım da 1 sayılır; yola `v1` olarak yazılır) | `exports.<i>.version` |
| Ad + sürüm | tekil; aynı ad farklı sürümlerle birlikte sunulabilir (geçiş süresi) | `exports.<i>.version` |
| `exports[].description` | `{ tr: 1-1000, en? }`, katı; kırpılmaz, yalnız boşluk olamaz; onay penceresinde yöneticiye gösterilir | `exports.<i>.description` |
| `exports[].permission` | isteğe bağlı; çağıran kullanıcıda aranan izin; uygulamanın ad alanında VE manifest'te beyanlı | `exports.<i>.permission` |
| `exports[].request`, `response` | sözleşme şeması (aşağıda) | `exports.<i>.request.<şema yolu>` |
| `imports[].app` | veren uygulamanın kısa adı (`SLUG_RE`, ≤ 24); kendi kısa adı olamaz | `imports.<i>.app` |
| `imports[].export`, `version` | dışa açım adı ve sürümü (aynı biçim) | `imports.<i>.export` |
| Uygulama + dışa açım | tek sürüm beyan edilir; sürümü beyan seçer (`readAppExport` sürüm almaz, `ONE_IMPORTS`'tan okur) | `imports.<i>.export` |
| `imports[].reason` | `{ tr: 1-1000, en? }`, katı; kırpılmaz, yalnız boşluk olamaz; neden gerektiği, onay penceresinde gösterilir | `imports.<i>.reason` |
| Öğeler | `strict` (bilinmeyen alan reddedilir) | öğenin yolu |
| Adet | `exports` beyanları yayında tek bir değişkenle betiğe taşınır (`ONE_EXPORTS`: her ad ve sürüm için sözleşme özeti ve izin); değişken en çok 5000 bayttır (yaklaşık 25-35 beyan, ad ve izin uzunluğuna göre). Aşan liste doğrulamada durur. `imports` aynı biçimde `ONE_IMPORTS` ile taşınır (her uygulama + dışa açım için sürüm; en çok 5000 bayt, yaklaşık 60 beyan) | `exports`, `imports` |

**Sözleşme şeması:** ajan araç girdisiyle **aynı alt küme** (yukarıda `agent`: anahtarlar, tipler, kapalı
nesneler, derinlik ≤ 8, nesnede ≤ 50 alan, JSON hali ≤ 8 KiB) ve buna ek olarak `maxItems`: **her dizide zorunlu**, 0 ile
1000 arası tam sayı. Kök `"type": "object"` olmak zorundadır. Aynı doğrulayıcı kullanılır (`boundedSchemaProblems`;
sözleşme profili `CONTRACT_SCHEMA_PROFILE`), ajan profili değişmedi: `maxItems` araç girdisinde yine reddedilir. Yanıt
gövdesi en çok 256 KB, istek gövdesi en çok 16 KB'tır (çalışma anında uygulanır).

**Sözleşme özeti (`contractDigest`):** onay kaydı sözleşmenin özetine bağlıdır. Özet; `description`, `permission` (yoksa
açıkça `null`), `request` ve `response` alanlarının RFC 8785 kanonik JSON'unun UTF-8 baytları üzerinden SHA-256'dır
(küçük harfli onaltılık). `name` ve `version` özete girmez, onay kaydı onları ayrıca taşır. Yanıta alan eklemek,
izni değiştirmek ya da açıklamayı değiştirmek özeti değiştirir; aynı sürümde bu değişiklik yayında reddedilir ve yeni
bir `version` ister (aşağıdaki yayın kuralları). Gerekçe de aynı biçimle ayrı bir özete bağlıdır (`reasonDigest`).
Onay yalnız şu üç durumda yeniden istenir: alan uygulama yeni bir sürüme geçtiğinde, alan uygulama gerekçeyi
(`imports[].reason`) değiştirdiğinde ve geri alma onaydakinden farklı bir sözleşmeyi geri getirdiğinde. Doğrulama özetin kapsadığı alanları değiştirmez (kırpma yok, bilinmeyen
anahtar reddedilir); bu yüzden özet ham manifestte ve doğrulanmış manifestte aynıdır. Özetin çeviremeyeceği değer
(ör. eşi olmayan vekil karakter) doğrulamada yola bağlı hatayla reddedilir. Özeti YALNIZ `packages/manifest/src/contract.ts` hesaplar; altın test
vektörleri (RFC 8785 örnekleri, FIPS 180-2 vektörleri, Türkçe metin, büyük sayı) çıktıyı kilitler. Backend aynı özeti
kendi kaydı için ayrıca hesaplar; iki uygulama aynı altın vektörlerle kilitlidir.

**Yayın kuralları:** platform yayını ve terfiyi, hat başlamadan önce yayınlanacak koddaki manifestle denetler.
Kural ihlalinde yayın `409` ile durur ve hat başlamaz:

| Kural | Hata |
|---|---|
| Aynı `name` + `version`'ın sözleşmesi (açıklama, izin, istek ya da yanıt şeması) değiştirilemez; en küçük değişiklik de yeni bir `version` ister | `ONE-APP-E-163` |
| Aynı portalda onaylı olarak okunan bir sürüm kaldırılamaz: önce alan uygulama yeni sürüme geçip onay alır, eski sürüm o süre boyunca birlikte sunulur | `ONE-APP-E-162` (alan uygulamayı adlandırır) |
| Manifest yayınlanacak commit'te yoksa, başka uygulamaya aitse ya da `exports` / `imports` geçersizse | `ONE-APP-E-164` |

Geri almada bu denetim yapılmaz: geri alınan sürümün beyanları geçerli olur. Sözleşmesi değişen onay yeniden onay ister,
kalkan sürümü okuyan uygulama onu okuyamaz.

## `services.dexa`: DEXA (ERP) sorguları ve işlemleri

Uygulama ERP'ye yalnız **önceden tanımlı QueryStore anahtarlarıyla** erişir. İki liste vardır ve en az biri
verilir (boş `services.dexa: {}` reddedilir):

- `queryKeys`: **okuma** sorguları (uç `One/services/dexa/query`, scope `dexa.read`); SDK `queryDexa`.
- `commandKeys`: ERP'ye **YAZAN** işlemler (uç `One/services/dexa/command`, scope `dexa.write`); SDK
  `runDexaCommand` (zorunlu `Idempotency-Key`). Ayrıntı: SDK başvurusu (`/docs/sdk`).

| Kural | Değer | Hata yolu |
|---|---|---|
| Öğe biçimi (iki liste) | `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$` (`DEXA_QUERY_KEY_RE`; harf duyarlı; SDK `DEXA_KEY_RE` ile birebir, SDK testi kilitler) | `services.dexa.queryKeys.<i>`, `services.dexa.commandKeys.<i>` |
| Adet | `queryKeys` en az 1, en çok 64 (`DEXA_QUERY_KEYS_MAX`); `commandKeys` en az 1, en çok 32 (`DEXA_COMMAND_KEYS_MAX`; backend kayıt defteri tavanıyla aynı) | listenin yolu |
| Tekillik | aynı anahtar bir listede iki kez yazılamaz (harf duyarlı: `A` ile `a` ayrıdır) | yinelenen öğenin yolu |
| En az bir liste | `services.dexa` varsa `queryKeys` ya da `commandKeys` olmalı | `services.dexa` |
| Çapraz denetim | `queryKeys` varsa `scopes` içinde `dexa.read` olmalı; `commandKeys` varsa `dexa.write` olmalı | `scopes` |
| Çapraz denetim | `scopes` içinde `dexa.read` varsa `queryKeys` olmalı; `dexa.write` varsa `commandKeys` olmalı | `services.dexa` |

⛔ İki liste de bir **beyandır**: çalışma anında okunmaz ve bir anahtarı açmaz. Hangi anahtarın çalışabileceğinin tek
kaynağı **platform kayıt defteridir**. Anahtarları Devoplus Console'da uygulamanın kaydına yönetici ekler (`dexaQueryKeys`
/ `dexaCommandKeys`); kayıtta olmayan sorgu anahtarı backend'den 403 `ONE-APP-E-024`, işlem anahtarı 403 `ONE-APP-E-030`
alır. Bir işlem anahtarı ayrıca koddaki ERP komut kataloğunda One uygulamalarına **açık** olmalıdır (bugün böyle bir
girdi yoktur; açmak bir backend kod değişikliğidir). Anahtar adı uydurulmaz; QueryStore tanımından alınır.

Çapraz denetimin gerekçesi: scope'suz beyan edilen anahtar listesi hiç çalışmaz (belirteç 403 `ONE-APP-E-017` alır),
listesiz beyan edilen `dexa.read`/`dexa.write` ise uygulamaya kullanmadığı bir ERP okuma ya da **yazma** yetkisi taşıtır.
İskelet üretici `--scopes dexa.read` ve `--scopes dexa.write` kabul etmez (anahtar uyduramaz); scope ve anahtarlar
iskelet üretildikten sonra elle eklenir.

## `widgets`: Portal pano widget'ları

Uygulama, Portal ana sayfasındaki panolara eklenebilen widget'lar beyan eder. Uygulama bir portala kurulunca widget'ları
o portalın pano tasarımcısında görünür; kaldırılınca düşer. İki tür vardır:

- **Veri widget'ı** (`stat`, `list`, `chart`, `table`): uygulama sabit biçimli veri döner, Portal kendi bileşenleriyle
  çizer. Tarayıcı uygulamanın adresine istek atmaz; veriyi Devoplus ister.
- **Sayfa widget'ı** (`page`): uygulamanın `path` yolundaki küçük sayfası panoda çerçeve içinde açılır.

⚠️ **Durum:** beyan ve doğrulama (`packages/manifest/src/widgets.ts`) ile veri widget'ının uygulama tarafındaki işleyicisi
(SDK `defineWidgets` ve `handleWidgetRequest`) hazırdır. Devoplus katalog kaydı ve pano tasarımcısı yazılıyor; o taraf
yayına alınana kadar bu alan hiçbir widget göstermez.

| Alan | Kural |
|---|---|
| `name` | Küçük harfle başlar, küçük harf/rakam/`-`, 1-40 karakter (dışa açım adıyla aynı kural). Adres yoluna olduğu gibi yazılır. |
| `version` | Başında sıfır olmayan pozitif tam sayı, en çok 999999. Aynı ad birden çok sürümle sunulabilir; aynı ad + sürüm iki kez yazılamaz. |
| `title` | `{ tr, en? }`, en çok 200 karakter. Tasarımcı kataloğunda ve widget başlığında görünür. |
| `kind` | `stat`, `list`, `chart`, `table` ya da `page`. |
| `size` | `{ w, h }`: panoya eklenirken başlangıç boyutu (sütun ve satır), ikisi de 2-12 arası tam sayı (pano 12 sütundur). |
| `permission?` | Widget'ı görmek için gereken izin; uygulamanın ad alanında ve `permission.code` ya da `permissions` içinde beyan edilmiş olmalı. |
| `refreshSeconds?` | `0` (yalnız açılışta yüklenir) ya da 60-3600 saniye. |
| `params?` | Tasarımcıda seçilecek ayarların şeması (aşağıda). Yoksa widget'ın ayarı yoktur. |
| `path?` | Yalnız `page` türünde ve orada zorunlu: `/` ile başlar, en az bir bölüm taşır; yalnız küçük harf, rakam, `-` ve `/`. Kök yol (`/`), sorgu (`?`), `#`, `%` ve `.` kabul edilmez. Yol menüde (`navigation`) yer alamaz ve iki farklı sayfa widget'ı aynı yolu paylaşamaz. |

**Ayar şeması (`params`):** `exports` sözleşme şemasıyla aynı sınırlı biçimdir (kök `"type": "object"`, her dizide
`maxItems` zorunlu, derinlik ≤ 8, nesnede ≤ 50 alan, JSON hali ≤ 8 KiB) ve buna ek olarak **her alanda `title` zorunludur**:
`{ "tr": "...", "en": "..." }` (TR zorunlu, EN isteğe bağlı, en çok 200 karakter). Tasarımcı formu alan etiketini buradan çizer.
`title` yalnız widget ayar şemasında tanınır; araç girdisi ve dışa açım şeması onu reddeder.

```json
"widgets": [
  {
    "name": "low-stock",
    "version": 1,
    "title": { "tr": "Azalan stok", "en": "Low stock" },
    "kind": "table",
    "size": { "w": 6, "h": 4 },
    "permission": "One.Stock.Read",
    "refreshSeconds": 300,
    "params": {
      "type": "object",
      "properties": { "branch": { "type": "string", "maxLength": 20, "title": { "tr": "Şube", "en": "Branch" } } },
      "required": ["branch"]
    }
  },
  { "name": "summary", "version": 1, "title": { "tr": "Özet" }, "kind": "page", "size": { "w": 4, "h": 3 }, "path": "/widget/ozet" }
]
```

**Widget sözleşmesi (`widgetContractDigest`):** `kind`, `params` şeması, `permission` (yoksa `null`) ve `path` (yoksa `null`)
alanlarının dışa açımla aynı kanonik biçimdeki SHA-256 özeti. `title`, `size` ve `refreshSeconds` özete girmez: değişmeleri
panoya eklenmiş widget'ı bozmaz. Pano kaydı widget'ı adı ve sürümüyle tutar; aynı sürümde sözleşmeyi kırıcı biçimde
değiştirmek (türü, yolu değiştirmek, ayara zorunlu alan eklemek, alan türünü değiştirmek) yeni bir `version` ister.

## `menu.icon`: menü simgesi

Portal menüsünde uygulamanın yanında çizilen simge (geliştirici kararı 25.10.2026). Değer bir **PrimeIcons sınıfıdır**:
`pi pi-` öneki ve simgenin adı (`pi pi-box`, `pi pi-flag`, `pi pi-wrench`). İzinli liste PrimeIcons 7.0.0'ın tamamıdır
(314 simge; `packages/manifest/src/menuIcons.ts`).

- Backend simgeyi **her yayın isteğinde** yayımlanan kodun manifestinden alır. Simge değişikliği bir sonraki yayında
  menüye geçer; menü kullanıcı başına 10 dakika önbelleklidir.
- Alan yoksa menüde varsayılan simge (`pi pi-box`) çizilir.
- Listede olmayan değer (eski Feather adı `box`, büyük harf, fazladan boşluk ya da ikinci sınıf) **reddedilmez**: menüde
  varsayılan simge çizilir; one-ci arayüz kapısı ve kaynak yüklemesi uyarı yazar.
- "Devoplus One Uygulamalarım" öğesinin simgesi sabittir (`pi pi-th-large`).

## Mobil bildirim: `push.execute`

`scopes` içinde `push.execute` (SDK `sendPush`, uç `One/services/push`; ⚠️ geliştirici onayı bekliyor). E-posta/SMS
scope'ları gibi bir servis listesi ya da çapraz denetimi yoktur; `services.push` bayrağı bugün hiçbir kaynağa çevrilmez
(aşağıdaki sınırlar).

## `permissions`: uygulama içi ek izinler

Uygulama içinde rol gerekiyorsa (ör. yalnız onaylayanlar onaylasın) ek izin kodu beyan edilir. Portal yöneticisi bu
kodu Yetki Yönetimi'nde **kullanıcı gruplarına** atar; grup üyeliği org düğümlerinden (şirket, departman, ünvan,
pozisyon, iş yeri, çalışan türü) sunucuda çözülür. Dispatch kullanıcının **yalnız bu uygulamanın ad alanındaki**
izinlerini `X-One-Permissions` ile iletir; SDK `hasPermission(ctx, kod)` / `requirePermission(ctx, kod)` okur (portal
yöneticisi her izne sahip sayılır; bu, platformun yönetici baypasıdır).

| Kural | Değer | Hata yolu |
|---|---|---|
| Kod biçimi | `PERMISSION_CODE_RE` | `permissions.<i>.code` |
| Ad alanı | `permission.code` ile aynı önek: `One.Hello.Use` için `One.Hello.` (`permissionNamespace`) | `permissions.<i>.code` |
| Tekillik | ana koddan ve birbirinden farklı | `permissions.<i>.code` |
| Adet | en çok 16 (`EXTRA_PERMISSIONS_MAX`; backend `OneNaming.AdditionalPermissionMaxCount` ile aynı) | `permissions` |
| Öğe | `strict` (bilinmeyen alan reddedilir) | öğenin yolu |

⛔ `permissions` bir **beyandır**: kodu kataloğa ekleyen şey platform kayıt defteridir (Devoplus Console'da uygulamanın
kaydına aynı kodlar girilir; backend `additionalPermissions`). Kayıtta olmayan kod hiçbir gruba atanamaz ve
`hasPermission` yalnız portal yöneticisinde `true` döner. Ad alanı uygulamalar arasında tekildir (backend 409
`ONE-APP-E-028`).

## `navigation`, `actingSessions`, `devicePermissions`

**`navigation`**: uygulama sol menü çizmez; görünümler burada beyan edilir ve Portal iframe'in üstünde sekme olarak
çizer. Portal adresi `/One/<kısa ad>/<yol>` olur ve iframe aynı yola gider; uygulamanın sunucusu bu yolların hepsinde
sayfayı döndürür. SDK `navigationFromManifest(manifest)` listeyi sayfa betiğine gömer, köprü `one:navigation` ile
Portal'a bildirir.

| Kural | Değer | Hata yolu |
|---|---|---|
| Yol | `/` ya da `/` ile ayrılmış parçalar; parça harf/rakam/`-`/`_` ile başlar, `.` içerebilir (`NAVIGATION_PATH_RE`); sorgu, `#`, `..`, `//` YOK; ≤ 200 karakter. SDK `ROUTE_PATH_RE` ile birebir (SDK testi kilitler) | `navigation.<i>.path` |
| Etiket | `{ tr, en? }` (1-120) | `navigation.<i>.label.tr` |
| Tekillik | aynı yol iki kez yazılamaz | `navigation.<i>.path` |
| Adet | en çok 12 (`NAVIGATION_MAX_ITEMS`) | `navigation` |
| İzin (`permission`, isteğe bağlı) | `One.<Uygulama>.<Eylem>`; uygulamanın ad alanında ve `permission.code` ya da `permissions[]` içinde **beyan edilmiş** olmalı (araç izniyle aynı kural). Verilirse sekme yalnız bu izni taşıyana ve yöneticiye görünür: köprü sekmeyi oturumdaki kişinin izinleri `one.setNavigationAccess` ile bildirilene kadar menüye koymaz, izin kodu Portal'a gitmez. Yalnız görünürlüktür; veri sunucu ucunda ayrıca korunur | `navigation.<i>.permission` |
| Öğe | `strict` | öğenin yolu |

**`actingSessions`**: `read` (varsayılan): Vekalet ya da Yerine Görüntüle oturumunda dispatch GET/HEAD dışındaki
isteği **403 `ONE-DSP-E-009`** ile reddeder (backend genel kuralı: okuma açık, yazma kapalı). `write`: bu oturumlarda da
yazılır; kim adına kimin yazdığı SDK `ctx.user.sub` + `ctx.actor.sub` ile kaydedilmelidir. Değer kurulum kaydına
(`one-ci deploy`) yazılır, dispatch oradan okur, çalışma anında uygulama değiştiremez. Backend beyanı outbound'un
eklediği `X-One-Acting-Writes` başlığından tanır (yalnız platform anahtarı eşleşirse); `write` beyanı Devoplus API'nin
genel uçlarını açar, ama ERP komutu, e-posta, SMS ve mobil bildirim bu oturumlarda yine KAPALIDIR. Denetim kaydı
(`recordAudit`) her iki değerde de yazılır ve vekili ayrıca tutar.

**`devicePermissions`**: dispatch her yanıta `Permissions-Policy: camera=(), microphone=(), geolocation=()` ekler;
beyan edilen özellik `(self)` olur. ⛔ Alan adı `permissions` DEĞİLDİR: o ad uygulama içi izin kodlarına
aittir. iframe'de özellik ayrıca Portal'ın iframe `allow` niteliğine bağlıdır.

## `jobs`: zamanlanmış görev ve kuyruk

Uygulama işi **beyan eder ve işleyicisini yazar** (SDK `defineJobs` + `handleJobRequest`); işi platform tetikler.
Zamanlama vadesi gelince, kuyruk ise uygulama `enqueue(ad, gövde, { delaySeconds })` ile ileti bırakınca çalışır.
Platform işleyiciyi `POST /__one/jobs/<schedule|queue>/<ad>` ile çağırır; bu yol internetten kapalıdır.

| Kural | Değer | Hata yolu |
|---|---|---|
| Ad | `^[a-z][a-z0-9-]{1,39}$` (`JOB_NAME_RE`; SDK ile birebir), tür içinde tekil | `jobs.schedules.<i>.name`, `jobs.queues.<i>.name` |
| `cron` | 5 alan: dakika saat ayın-günü ay haftanın-günü. Öğe `*`, `n`, `a-b`, `*/s`, `a-b/s` ya da `a/s` olur, öğeler virgülle listelenir; ay adları `jan` ile `dec` arası, gün adları `sun` ile `sat` arası; haftanın günü 0-7 (0 ve 7 pazar). `?`, `L`, `W`, `#` ve saniye alanı YOK. Ayın günü ile haftanın günü ikisi de kısıtlıysa biri tutması yeter. En sık aralık dakikada bir | `jobs.schedules.<i>.cron` |
| `timeZone` | IANA adı (`Europe/Istanbul`, `UTC` vb.), varsayılan `Europe/Istanbul`; kanonik yazıma çevrilir (`europe/istanbul` değeri `Europe/Istanbul` olur). Sayısal uzaklık (`+03:00`) kabul edilmez | `jobs.schedules.<i>.timeZone` |
| `maxRetries` | 0-10 tam sayı, varsayılan 3; işleyici 5xx döndürür ya da zaman aşımına uğrarsa kaç kez yeniden denenir. Zamanlama için alan yoktur: 3 kez | `jobs.queues.<i>.maxRetries` |
| `description` | 1-200 karakter, isteğe bağlı; yalnız beyandır (platforma gitmez) | öğenin yolu |
| Adet | en çok 10 zamanlama + 10 kuyruk; ikisi de boşsa `jobs` yazılmaz | `jobs.schedules`, `jobs.queues`, `jobs` |
| `services` | isteğe bağlı; yalnız `mail.execute`, `sms.execute`, `push.execute`, `dexa.read`, `ai.use` (`JOB_SERVICE_SCOPES`), tekil; her biri **`scopes` içinde de** beyanlı. `dexa.write` ve diğer her scope reddedilir. Beyan istektir: servis kurulumda portal yöneticisi onaylamadıkça açılmaz. one-ci kurulum kaydına `jobServices` olarak yazar | `jobs.services.<i>` |
| Öğe | `strict` (bilinmeyen alan reddedilir) | öğenin yolu |

Çalışma kuralları (ayrıntı `/docs/jobs.md`): kuyruk iletisi gövdesi JSON ≤ 64 KiB, gecikme 0-86.400 sn; beyan edilmemiş
kuyruk adına ileti bırakılamaz. Kuyruk işi iletiyi bırakan kullanıcının kimliğini bilgi olarak taşır, zamanlanmış iş
kullanıcısız koşar; ikisinde de Devoplus servisleri uygulamanın kimliğiyle ve yalnız `services` + kurulum onayıyla
çağrılır. İşleyici 2xx dönerse iş tamamdır; 4xx kalıcı hatadır (yeniden denenmez); 5xx ya da zaman aşımı
geri çekilmeyle yeniden denenir. Aynı iş birden çok kez gelebilir; `job.id` her denemede aynıdır.

## `sdkVersion`: SDK sürümü beyanı

Uygulamanın derlendiği SDK sürümü (ör. `0.3.0-next.12`; `v` öneki ve `+` ile başlayan derleme üstverisi yok). **Elle yazılmaz:**
SDK 0.3.0 ve sonrasında iskelet üretici (`create-devoplus-one-app`) ve `npx one upgrade` yazar; daha eski SDK'lı uygulamada
alan boş kalır ve bu yalnız uyarıdır. Yayın kapısı (`one-ci validate`) beyanı uygulamanın **kurulu** SDK sürümüyle (kilit
dosyası ya da `node_modules`) karşılaştırır:

| Durum | Sonuç | Kural |
|---|---|---|
| Beyan kurulu sürüme eşit | geçer | yok |
| Beyan kurulu sürümden farklı | **yayın durur** | ONE-RULE-007 |
| Beyan yok, kurulu SDK 0.3.0 ön sürümleri ya da sonrası | **yayın durur** | ONE-RULE-008 |
| Beyan yok, kurulu SDK daha eski ya da okunamıyor | uyarı, yayın sürer | ONE-RULE-008 |
| Beyan var ama kurulu sürüm kesin okunamıyor (kilit dosyası yok) | uyarı, yayın sürer | yok |

Beyan yoksa sürüm defterine ve kurulum kaydına `legacy` yazılır; `legacy` her sürümden küçük sayılır. Alan yalnız yayın
kapısı ve gösterim içindir: çalışma anında platform davranışı bu alandan seçilmez. Kural metinleri ve düzeltme yönergesi
platform kuralları kataloğundadır (`packages/rules/catalog.json`); `one-ci validate` uyguladığı katalog revizyonunu ve uymayan
her kuralın tam metnini yazar.

## Bilinen sınırlar

- Katılık **kök** nesnede, `services`'te, `navigation` öğelerinde `agent` (bölüm + araç öğeleri), `exports` ve `imports` öğelerinde ve `jobs` (öğeler dahil) içindedir (`services: { aiGateway: true }` artık reddedilir). Diğer alt
  nesnelerde (`app`, `services.dexa`, `menu`, `permission`, `limits`) bilinmeyen alan reddedilmez, **sessizce atılır**
  (ölçüldü 15.10.2026).
- `services.email`, `sms`, `push` ve `orgSchema` bugün bu depoda hiçbir kaynağa çevrilmez (okundu 15.10.2026:
  `one-ci plan` yalnız `database`, `files` ve `cache` okur). E-posta/SMS'i açan şey scope'tur (`mail.execute` / `sms.execute`).
