diff --git a/blog/2026-07-27-v0-0-76.md b/blog/2026-07-27-v0-0-76.md new file mode 100644 index 0000000..98a7790 --- /dev/null +++ b/blog/2026-07-27-v0-0-76.md @@ -0,0 +1,154 @@ +--- +slug: release-v0-0-76 +title: Release v0.0.76 +authors: [vnext-team] +tags: [release, duyuru, tasks, caching, ai] +date: 2026-07-27 +--- + +## Overview + +This release adds AI to the task palette and sharpens the caching and concurrency primitives. A new **Dapr Conversation task** (`TaskType = 20`) invokes an LLM/AI provider (OpenAI, Anthropic, Bedrock, …) provider-agnostically through the Dapr Conversation building block ([#844](https://github.com/burgan-tech/vnext/issues/844)). **Function result caching gains vary-by headers** — `varyByHeaders` / `varyByHeaderPrefixes` feed a `varyKey(context)` key-expression helper so one function keeps separate cache variants per header value ([#839](https://github.com/burgan-tech/vnext/issues/839)). Flow-level cache tuning is **consolidated under a single `attributes.config` object** (`config.functionCache.ttlSeconds`), the author-controlled TTL for the built-in instance functions ([#846](https://github.com/burgan-tech/vnext/issues/846)). **Distributed resource locks** become production-ready with **idempotent release and automatic terminal cleanup** ([#840](https://github.com/burgan-tech/vnext/issues/840)), plus a per-subInstance lock key for subflow terminal-outcome propagation ([#845](https://github.com/burgan-tech/vnext/issues/845)). **Wizard states are now treated like normal states** in the State/View functions ([#838](https://github.com/burgan-tech/vnext/issues/838)). The release also fixes author `Content-Type` preservation ([#847](https://github.com/burgan-tech/vnext/issues/847)), case-insensitive request-header normalization ([#841](https://github.com/burgan-tech/vnext/issues/841)), and init mappings support for publish ([#848](https://github.com/burgan-tech/vnext/issues/848)). This release runs on component **schema 0.0.51**. + +{/* truncate */} + +--- + +## Features + +### Dapr Conversation task — provider-agnostic LLM calls (#844) + +A new task type, **DaprConversation** (`TaskType = 20`), invokes an LLM/AI provider through Dapr's Conversation building block — the same sidecar the other Dapr task types use. The provider and its credentials live in a **domain-owned** Dapr `conversation` component (e.g. `openai`); the task only references the component name and the messages. + +```json +{ + "attributes": { + "type": "20", + "config": { + "componentName": "openai", + "inputs": [ + { "role": "system", "content": "You summarize customer complaints." }, + { "role": "user", "content": "Summarize the complaint in two sentences." } + ], + "parameters": { "model": "gpt-4o-mini", "maxTokens": "512" }, + "temperature": 0.2, + "scrubPII": true + } + } +} +``` + +- Required config is just `componentName`; `inputs` is an array of `role`/`content` messages, and `parameters` carries provider-specific string values (`model`, `maxTokens`, …). +- Optional `metadata`, `contextId` (stateful conversations), `temperature`, `scrubPII`, and `timeoutSeconds` (default 30). +- The runtime component is installed via the helm chart ([vnext-helm-charts #28](https://github.com/burgan-tech/vnext-helm-charts/pull/28)), so shipping a new provider needs no runtime redeploy. + +> **Reference:** issue [#844](https://github.com/burgan-tech/vnext/issues/844), helm PR [vnext-helm-charts #28](https://github.com/burgan-tech/vnext-helm-charts/pull/28) — see also the new [Dapr Conversation Task](/docs/components/tasks/dapr-conversation) component page. + +### Function cache vary-by headers (#839) + +The function `attributes.cache` block gains two fields — **`varyByHeaders`** (exact request-header names) and **`varyByHeaderPrefixes`** (header-name prefixes) — that fold the named headers into the cache key. They form the header-name set for the `varyKey(context)` key-expression helper (the two lists are unioned), so a single read function keeps **separate cache variants per header value** without the runtime baking in any header convention. When the instance does not supply its own `Instance.Data["varyBy"]`, these domain-declared fields are used. + +> **Reference:** issue [#839](https://github.com/burgan-tech/vnext/issues/839) — see also [Custom Functions → Cache](/docs/components/functions/custom). + +### Flow-level cache config object (#846) + +Author-controlled, flow-scoped cache tuning is now consolidated under a single **`attributes.config`** object. Its first member, **`functionCache.ttlSeconds`**, is the TTL for this workflow's built-in instance functions (`data`, `view`, `schema`, …). + +```json +{ "config": { "functionCache": { "ttlSeconds": 120 } } } +``` + +- Repeat calls for the **same instance** are served from cache for the TTL window; when the **instance changes, the cache drops** and the next call is re-cached. +- The host default is **60s** when `config` (or `ttlSeconds`) is absent or non-positive. +- The **State Function is excluded** — the platform manages its cache separately (host-side `StateFunctionCache`). + +> **Reference:** issue [#846](https://github.com/burgan-tech/vnext/issues/846) — see also [Workflow component → Config](/docs/components/workflow). + +### Distributed resource locks — idempotent release + automatic cleanup (#840, #845) + +Start, state-level, and shared transitions can declare an optional **`resourceLock`** block — a distributed lock (Dapr `lock.redis`, via the Aether SDK) that prevents concurrent instances from mutating a shared resource (a seat, daily limit, account, …). It runs in the **Manual** profile only, is owned by the `instanceId`, and always carries a TTL. + +```json +{ + "key": "reserve-daily-limit", + "target": "limit-reserved", + "triggerType": 0, + "versionStrategy": "Patch", + "resourceLock": { + "keyExpression": { "location": "./src/DailyLimitKey.csx", "code": "", "type": "L", "encoding": "NAT" }, + "action": "Acquire", + "ttlSeconds": 300, + "onConflict": "Abort" + } +} +``` + +- The recommended model is **`Acquire` on the check/entry transition** and let the runtime release it: when the instance reaches a terminal state (Completed / Faulted / Cancelled), its locks are **released automatically** — no manual `Release` on every terminal transition. +- **`Release` is idempotent/best-effort** — `LockDoesNotExist` counts as success, a foreign lock is logged (not faulted), and lock cleanup never rolls back a successful business transition. +- A conflict on `Acquire` aborts the transition and returns **HTTP 409** (the instance is marked faulted `F`); the caller retries. `Extend` is unreliable (no native Dapr extend) — size the TTL to cover the whole operation. +- Subflows now derive a **per-subInstance lock key** so terminal-outcome propagation stays correct across nested instances ([#845](https://github.com/burgan-tech/vnext/issues/845)). + +> **Reference:** issues [#840](https://github.com/burgan-tech/vnext/issues/840), [#845](https://github.com/burgan-tech/vnext/issues/845) — see also the new [Resource Lock guide](/docs/how-to/resource-lock) and [Workflow component → Transition](/docs/components/workflow). + +### Wizard states behave like normal states in read functions (#838) + +The State and View functions now treat **Wizard states like normal states**: authorization/role evaluation and the available-transition list are resolved the same way, so a wizard state's `state`/`view` response is consistent with any other state. This removes the special-casing that previously diverged wizard-state reads. + +> **Reference:** issue [#838](https://github.com/burgan-tech/vnext/issues/838) — see also [Workflow component → Wizard State ve View Davranışı](/docs/components/workflow). + +--- + +## Fixes + +- **Author `Content-Type` preserved without charset suffix** — function output scripts that set `content-type` keep the author value verbatim (the `; charset=...` suffix is no longer appended) ([#847](https://github.com/burgan-tech/vnext/issues/847)). +- **Case-insensitive request-header normalization** — transition request headers are matched case-insensitively, so header lookups no longer miss on casing differences ([#841](https://github.com/burgan-tech/vnext/issues/841)). +- **Init mappings support for publish** — the init/publish path handles mapping components correctly ([#848](https://github.com/burgan-tech/vnext/issues/848)). + +--- + +## Configuration Updates + +Configuration for v0.0.76: + +```json +{ + "runtimeVersion": "0.0.76", + "schemaVersion": "0.0.51" +} +``` + +> **Note:** Schema version advances to **0.0.51** (from `0.0.50`). The bump adds the DaprConversation task type `"20"` ([#844](https://github.com/burgan-tech/vnext/issues/844)), the function-cache `varyByHeaders` / `varyByHeaderPrefixes` fields ([#839](https://github.com/burgan-tech/vnext/issues/839)), the workflow-level `attributes.config` object (`config.functionCache`) ([#846](https://github.com/burgan-tech/vnext/issues/846)), and the transition `resourceLock` definition ([#840](https://github.com/burgan-tech/vnext/issues/840)). Update `@burgan-tech/vnext-schema` in your domain project before validating against this runtime. + +**Container images:** published at tag `0.0.76` under `ghcr.io/burgan-tech/vnext/*`, Cosign-signed (keyless OIDC) with SBOM + provenance. Immutable digests are listed in the [GitHub release](https://github.com/burgan-tech/vnext/releases/tag/v0.0.76). + +--- + +## Issues Referenced + +- [vnext #844](https://github.com/burgan-tech/vnext/issues/844) — Dapr Conversation (AI/LLM) task type. +- [vnext #839](https://github.com/burgan-tech/vnext/issues/839) — `varyKey` cache-key helper + config-driven vary-by headers. +- [vnext #846](https://github.com/burgan-tech/vnext/issues/846) — Consolidate flow-level `functionCache` under a `config` object. +- [vnext #840](https://github.com/burgan-tech/vnext/issues/840) — Resource lock: idempotent release + automatic terminal cleanup. +- [vnext #845](https://github.com/burgan-tech/vnext/issues/845) — Retryable 503 + per-subInstance lock key for terminal-outcome propagation. +- [vnext #838](https://github.com/burgan-tech/vnext/issues/838) — Treat wizard states like normal states in State/View functions. +- [vnext #847](https://github.com/burgan-tech/vnext/issues/847) — Preserve author `Content-Type` without charset suffix. +- [vnext #841](https://github.com/burgan-tech/vnext/issues/841) — Normalize request headers case-insensitively. +- [vnext #848](https://github.com/burgan-tech/vnext/issues/848) — Init mappings support for publish. +- [vnext-helm-charts #28](https://github.com/burgan-tech/vnext-helm-charts/pull/28) — Dapr Conversation helm component. + +--- + +## Summary + +- **Dapr Conversation task (Type 20)** invokes an LLM/AI provider provider-agnostically through the Dapr Conversation building block; the provider is a domain-owned Dapr component. +- **Function cache vary-by headers**: `varyByHeaders` / `varyByHeaderPrefixes` feed the `varyKey(context)` helper for per-header cache variants. +- **Flow-level `config.functionCache.ttlSeconds`**: author-controlled TTL for built-in instance functions (host default 60s; State Function excluded). +- **Distributed resource locks** are production-ready: idempotent release, automatic terminal cleanup, HTTP 409 on conflict, and a per-subInstance lock key for subflows. +- **Wizard states** are read like normal states in the State/View functions. +- **Fixes**: author `Content-Type` preserved, case-insensitive header matching, init mappings support for publish. +- **Schema** is **0.0.51**. + +--- + +**vNext Runtime Platform Team** +Released July 27, 2026 diff --git a/docs/components/functions/custom.md b/docs/components/functions/custom.md index cdf510c..225eee5 100644 --- a/docs/components/functions/custom.md +++ b/docs/components/functions/custom.md @@ -251,6 +251,12 @@ Cache, fonksiyon başına **opt-in**'dir ve yalnızca **yan etkisiz (read) fonks | `bypassOnCacheError` | `boolean` | Hayır | `true` | `true`: cache okuma/yazma hataları isteği bozmaz, fonksiyon normal çalıştırılır. `false`: cache hatası isteği başarısız kılar | | `generationKeyExpression` | `object` | Hayır | — | Generation stamp'inin tutulduğu state key'ini çözen Dynamic Expresso ifadesi. `generationKey`'den önceliklidir | | `generationKey` | `string` | Hayır | — | Generation stamp'ini tutan statik state key'i | +| `varyByHeaders` | `string[]` | Hayır | — | Sonucu değiştiren **tam (exact)** request-header adları. Belirtilen header'lar cache key'ine katılır | +| `varyByHeaderPrefixes` | `string[]` | Hayır | — | Sonucu değiştiren request-header adı **prefiksleri** (ör. bir prefix ile başlayan tüm header'lar) | + +:::tip Vary-by header'lar (`#839`) +`varyByHeaders` ve `varyByHeaderPrefixes`, key-expression içinde kullanılabilen `varyKey(context)` yardımcı fonksiyonunun header-adı kümesini oluşturur (ikisi birleştirilir). Böylece runtime herhangi bir header konvansiyonuna bağlı kalmadan; aynı fonksiyonun farklı header değerleri için **ayrı cache varyantları** tutar. Instance `Instance.Data["varyBy"]` ile kendi kümesini sağlamazsa, domain'de tanımlı bu iki alan kullanılır. +::: ### Generation-Namespace Invalidation diff --git a/docs/components/tasks/dapr-conversation.md b/docs/components/tasks/dapr-conversation.md new file mode 100644 index 0000000..20e8dd7 --- /dev/null +++ b/docs/components/tasks/dapr-conversation.md @@ -0,0 +1,141 @@ +--- +sidebar_position: 15 +title: Dapr Conversation Task +description: Dapr Conversation building block üzerinden LLM/AI sağlayıcı çağrısı yapan task +--- + +# Dapr Conversation Task (Type: `20`) + +Dapr Conversation Task (`type: "20"`), bir **LLM/AI sağlayıcısını** Dapr'ın **Conversation building block**'u üzerinden çağırır. Böylece iş akışları; OpenAI, Anthropic, AWS Bedrock gibi sağlayıcılara **sağlayıcı-bağımsız** tek bir arayüzle prompt gönderebilir — diğer Dapr task türlerinin kullandığı aynı Dapr sidecar üzerinden. + +Sağlayıcı seçimi ve kimlik bilgileri **domain'e ait** bir Dapr `conversation` component'inde (ör. `openai`) tanımlanır; task yalnızca o component'in adını (`componentName`) ve mesajları (`inputs`) referans verir. Runtime bileşenini eklemek için bkz. [vnext-helm-charts #28](https://github.com/burgan-tech/vnext-helm-charts/pull/28). + +## Görev Tanımı + +> **Schema:** `task-definition.schema.json` + +```json +{ + "key": "summarize-complaint", + "version": "1.0.0", + "domain": "core", + "flow": "sys-tasks", + "flowVersion": "1.0.0", + "tags": ["ai", "llm", "conversation"], + "attributes": { + "type": "20", + "config": { + "componentName": "openai", + "inputs": [ + { "role": "system", "content": "Sen bir müşteri şikayeti özetleyicisisin." }, + { "role": "user", "content": "Şikayet metnini 2 cümlede özetle." } + ], + "parameters": { + "model": "gpt-4o-mini", + "maxTokens": "512" + }, + "temperature": 0.2, + "scrubPII": true, + "timeoutSeconds": 30 + } + } +} +``` + +## Konfigürasyon Alanları + +| Alan | Tip | Zorunlu | Varsayılan | Açıklama | +|------|-----|---------|------------|----------| +| `componentName` | string | **Evet** | — | Dapr conversation component adı (yapılandırılmış LLM sağlayıcısı), ör. `openai` | +| `inputs` | array | Hayır | — | Konuşma girdileri: `role`/`content` mesajlarından oluşan dizi (aşağıya bakın) | +| `parameters` | object | Hayır | — | Sağlayıcıya özgü **string** parametreler (ör. `model`, `maxTokens`). Component'e olduğu gibi iletilir | +| `metadata` | object | Hayır | — | İstekle iletilen Dapr component metadata'sı (string değerler) | +| `contextId` | string | Hayır | — | Durumlu (stateful) bir konuşmayı sürdürmek için bağlam tanımlayıcısı | +| `temperature` | number | Hayır | — | Örnekleme sıcaklığı (sampling temperature) | +| `scrubPII` | boolean | Hayır | — | `true` ise sağlayıcıdan prompt ve yanıtlarda PII temizliği (scrub) istenir | +| `timeoutSeconds` | integer | Hayır | `30` | Timeout süresi (saniye, minimum: 1) | + +### `inputs` Mesaj Yapısı + +Her girdi bir rol/içerik mesajıdır: + +```json +{ "role": "user", "content": "...", "scrubPII": true, "name": "opsiyonel" } +``` + +| Alan | Değerler | Açıklama | +|------|----------|----------| +| `role` | `user`, `system`, `assistant`, `developer`, `tool` | Mesajın rolü | +| `content` | string | Mesaj içeriği | +| `scrubPII` | boolean (ops.) | Bu mesaj için PII temizliği | +| `name` | string (ops.) | Mesaj için opsiyonel ad | + +## Property Erişimi + +Değerler çoğunlukla statik konfigürasyon yerine input mapping içinde dinamik olarak atanır. + +| Property | Setter Metodu | Açıklama | +|----------|---------------|----------| +| `ComponentName` | `SetComponentName(string componentName)` | Conversation component adı | +| `Inputs` | `SetInputs(dynamic inputs)` | Mesaj dizisi | +| `Parameters` | `SetParameters(Dictionary parameters)` | Sağlayıcı parametreleri | +| `Metadata` | `SetMetadata(Dictionary metadata)` | Component metadata'sı | +| `ContextId` | `SetContextId(string? contextId)` | Bağlam tanımlayıcısı | +| `Temperature` | `SetTemperature(double? temperature)` | Örnekleme sıcaklığı | +| `ScrubPII` | `SetScrubPII(bool? scrubPII)` | PII temizliği | +| `TimeoutSeconds` | `SetTimeoutSeconds(int? timeoutSeconds)` | Timeout süresi | + +Input mapping örneği: + +```csharp +public class SummarizeComplaintMapping : ScriptBase, IMapping +{ + public Task InputHandler(WorkflowTask task, ScriptContext context) + { + var conversation = task as DaprConversationTask; + conversation.SetComponentName("openai"); + conversation.SetInputs(new[] + { + new { role = "system", content = "Sen bir müşteri şikayeti özetleyicisisin." }, + new { role = "user", content = (string)context.Instance.Data.complaintText } + }); + conversation.SetParameters(new Dictionary + { + ["model"] = "gpt-4o-mini", + ["maxTokens"] = "512" + }); + return Task.FromResult(new ScriptResponse()); + } + + public Task OutputHandler(ScriptContext context) + { + var summary = context.Body?.outputs?[0]?.result; + return Task.FromResult(new ScriptResponse { Data = new { summary } }); + } +} +``` + +## Standart Yanıt + +Yanıt, sağlayıcının döndürdüğü çıktı(lar)ı `outputs` altında taşır: + +```json +{ + "Data": { + "outputs": [ + { "result": "Müşteri, kartının teslim edilmediğini ve çağrı merkezine ulaşamadığını bildiriyor." } + ] + }, + "StatusCode": 200, + "IsSuccess": true, + "ErrorMessage": null, + "TaskType": "20" +} +``` + +## İlgili + +- [Tasks Genel Bakış](/docs/components/tasks/) — tüm task türleri ve referans mekanizması +- [DaprService Task](/docs/components/tasks/dapr-service) — aynı Dapr sidecar üzerinden service invocation +- Schema kaynağı: [task-definition.schema.json (vnext-schema)](https://github.com/burgan-tech/vnext-schema/blob/master/schemas/task-definition.schema.json) +- Helm component: [vnext-helm-charts #28](https://github.com/burgan-tech/vnext-helm-charts/pull/28) diff --git a/docs/components/tasks/index.md b/docs/components/tasks/index.md index 1df48c4..979ab7c 100644 --- a/docs/components/tasks/index.md +++ b/docs/components/tasks/index.md @@ -53,7 +53,7 @@ Her task tanımı `task-definition.schema.json` şemasına uyar. Zorunlu alanlar ## Görev Türleri -`task-definition.schema.json` toplamda **19 task türü** tanımlar: +`task-definition.schema.json` toplamda **20 task türü** tanımlar: | # | Görev Türü | Açıklama | Detay | |---|---|---|---| @@ -76,6 +76,7 @@ Her task tanımı `task-definition.schema.json` şemasına uyar. Zorunlu alanlar | 17 | **StateStoreTask** | Dapr state store ile cache (get/set/delete) | [StateStore](./state-store) | | 18 | **CacheAsideTask** | Read-through cache (miss'te sourceTask çalıştırıp cache'ler) | [CacheAside](./cache-aside) | | 19 | **GetInstanceTask** | Tek bir instance'ın tam projeksiyonunu (metadata + data) çekme | [GetInstance](./get-instance) | +| 20 | **DaprConversationTask** | Dapr Conversation ile LLM/AI sağlayıcı çağrısı | [DaprConversation](./dapr-conversation) | ## Görev Kullanımı diff --git a/docs/components/tasks/trigger.md b/docs/components/tasks/trigger.md index bed87e0..d913863 100644 --- a/docs/components/tasks/trigger.md +++ b/docs/components/tasks/trigger.md @@ -10,12 +10,17 @@ description: Workflow orchestration ve instance yönetim task'ları ## Task Türleri -- **StartTask** (Type: `11`) - Yeni iş akışı instance'ları başlatır -- **DirectTriggerTask** (Type: `12`) - Mevcut instance'larda transition tetikler -- **GetInstanceDataTask** (Type: `13`) - Instance verilerini alır -- **SubProcessTask** (Type: `14`) - Bağımsız subprocess instance'ları başlatır +Bu sayfada belgelenen dört trigger task'ı (bağlantılar aynı sayfadaki ilgili bölüme gider): -Aynı ailenin sorgu tarafındaki üyeleri ayrı sayfalarda belgelenmiştir: [GetInstances Task](/docs/components/tasks/get-instances) (Type: `15`, instance listesi) ve [GetInstance Task](/docs/components/tasks/get-instance) (Type: `19`, tek instance'ın tam projeksiyonu). +- [**StartTask** (Type: `11`)](#1-starttask-type-11) - Yeni iş akışı instance'ları başlatır +- [**DirectTriggerTask** (Type: `12`)](#2-directtriggertask-type-12) - Mevcut instance'larda transition tetikler +- [**GetInstanceDataTask** (Type: `13`)](#3-getinstancedatatask-type-13) - Instance verilerini alır +- [**SubProcessTask** (Type: `14`)](#4-subprocesstask-type-14) - Bağımsız subprocess instance'ları başlatır + +Aynı ailenin sorgu tarafındaki üyeleri ayrı sayfalarda belgelenmiştir: + +- [**GetInstances Task** (Type: `15`)](/docs/components/tasks/get-instances) - Filtre ile instance listesi +- [**GetInstance Task** (Type: `19`)](/docs/components/tasks/get-instance) - Tek instance'ın tam projeksiyonu (metadata + data) --- diff --git a/docs/components/workflow.md b/docs/components/workflow.md index f490e0e..77380fc 100644 --- a/docs/components/workflow.md +++ b/docs/components/workflow.md @@ -252,6 +252,7 @@ description: vNext Workflow component — tanım, türler, capability matrix ve | `queryRoles` | array | Hayır | Root-level sorgu rolleri. DENY her zaman ALLOW'u geçersiz kılar | | `output` New | object \| null | Hayır | Sync yanıt için opsiyonel output mapping (`scriptCode`, `IOutputHandler`). Ayrıntı: [Output Mapping](#output-mapping) | | `event` New | object \| null | Hayır | Workflow seviyesi event tanımı. Tanımlıysa harici bir event bu workflow'un **yeni bir instance'ını başlatabilir** (`action=start`). Transition seviyesi event'ten bağımsızdır. Ayrıntı: [Event Transition](#event-transition) | +| `config` New | object \| null | Hayır | Flow seviyesi yapılandırma. Şu an built-in function cache ayarını (`functionCache`) içerir. `null` ise host varsayılanları geçerlidir. Ayrıntı: [Config (Built-in Function Cache)](#config-built-in-function-cache) | --- @@ -304,9 +305,7 @@ Wizard state, kullanıcı girdisini transition tabanlı modellemek için kullan State Function aktif state'in tipini Wizard olarak değerlendirdiğinde önce authorization/role evaluation sonrasında kullanılabilir transition listesini belirler. Kullanılabilir manuel transition varsa View Function, state view yerine bu transition'ın view'ını döndürür. Transition üzerinde view tanımlı değilse state'de tanımlı view fallback olarak kullanılır. -Loop oluşmaması için State Function yanıtındaki kullanılabilir transition listesinde ilgili transition'ın `hasView` bilgisi `false` döner. Bu sayede client, state aşamasında zaten gösterilen transition view için tekrar transition view kontrolü yapmaz. - -Örneğin hesap açılışı akışında "hesap türü seçimi" state'inde kullanıcıdan vadeli/vadesiz seçimi alınacaksa bu seçim state view içinde veri alanı olarak modellenmemelidir. Seçim transition routing perspektifiyle tasarlanır; böylece her seçim ayrı transition görünürlüğü, loglama ve raporlama katkısı sağlar. +Örneğin hesap açılışı akışında "hesap türü seçimi" state'inde kullanıcıdan vadeli/vadesiz seçimi alınacaksa bu seçim state view içinde veri alanı olarak modellenmemelidir. Seçim transition routing perspektifiyle tasarlanır; böylece her seçim ayrı transition görünürlüğü, loglama ve raporlama katkısı sağlar. State view varsa, summary veya wizard'a devam edeceği ekran olarak kullanılmalıdır. ### `stateSubType` Enum Değerleri @@ -570,13 +569,14 @@ flowchart TD | `labels` | array | **Evet** | Çoklu dil etiketleri (`minItems: 1`) | | `schema` | object \| null | Hayır | Transition schema referansı (request body validation) | | `rule` | object \| null | **Koşullu** | Kural betiği. `triggerType: 1` (auto) ise **zorunlu** (triggerKind 10 hariç) | -| `timer` | object \| null | **Koşullu** | Timer betiği. `triggerType: 2` (scheduled) ise **zorunlu** | +| `timer` | object \| null | **Koşullu** | Timer betiği (`ITimerMapping`). `triggerType: 2` (scheduled) ise **zorunlu**. Schedule transition'ın nasıl timer ürettiği için bkz. [Timer mapping](/docs/components/mappings#timer-mapping) | | `view` | object \| null | Hayır | Transition view tanımı. Yalnızca `triggerType: 0` (manual) için geçerli | | `onExecutionTasks` | array | Hayır | Transition sırasında çalıştırılacak task listesi | | `mapping` | object \| null | Hayır | Transition input mapping betiği | | `roles` | array | Hayır | Yetkilendirme rolleri. DENY her zaman ALLOW'u geçersiz kılar | | `annotations` New | object \| null | Hayır | Client-side filtreleme ve UI bağlamı için key-value metadata. Platform annotations değerlerini yorumlamaz (passthrough). Çakışmaları önlemek için namespace'li key'ler kullanın (örn. `ui/visible-in`, `ui/priority`) | | `event` New | object \| null | **Koşullu** | Transition seviyesi event tanımı. `triggerType: 3` ise **zorunlu**. Ayrıntı: [Event Transition](#event-transition) | +| `resourceLock` New | object \| null | Hayır | Transition sırasında çalışan dağıtık kaynak kilidi (Dapr `lock.redis`). Yalnızca **Manual** profilde çalışır; start, state-level ve shared transition'larda geçerlidir. Ayrıntı: [Kaynak Kilitleme](/docs/how-to/resource-lock) | ### `triggerType` Enum Değerleri @@ -689,6 +689,7 @@ Kurallar: | `mapping` | object \| null | Hayır | Input mapping betiği | | `roles` | array | Hayır | Yetkilendirme rolleri | | `annotations` New | object \| null | Hayır | Client-side filtreleme ve UI bağlamı için key-value metadata (passthrough) | +| `resourceLock` New | object \| null | Hayır | Dağıtık kaynak kilidi. Ayrıntı: [Kaynak Kilitleme](/docs/how-to/resource-lock) | ### Davranış @@ -737,6 +738,7 @@ Birden fazla state'den erişilebilen **ortak transition**'lardır. Standart tran | `availableIn` New | string[] | Hayır | Transition'ın geçerli olduğu state key'leri. Tanımlanmazsa **tüm state'lerden** erişilebilir | | `annotations` New | object \| null | Hayır | Client-side filtreleme ve UI bağlamı için key-value metadata (passthrough) | | `event` New | object \| null | **Koşullu** | Event tanımı. `triggerType: 3` ise **zorunlu** — bkz. [Event Transition](#event-transition) | +| `resourceLock` New | object \| null | Hayır | Dağıtık kaynak kilidi. Ayrıntı: [Kaynak Kilitleme](/docs/how-to/resource-lock) | Shared transition'larda `triggerType` yalnızca `0` (Manual), `2` (Scheduled) veya `3` (Event) olabilir. @@ -803,6 +805,32 @@ Workflow (global), state ve task seviyesinde tanımlanabilir. Öncelik sırası: ## Diğer Yapılar +### Config (Built-in Function Cache) + +`attributes.config`, flow seviyesi yazar-kontrollü ayarları tek bir obje altında toplar. Şu an tek üyesi, built-in **instance function**'larının (`data`, `view`, `schema`, …) cache süresini ayarlayan `functionCache`'dir. + +```json +"config": { + "functionCache": { + "ttlSeconds": 120 + } +} +``` + +| Alan | Tip | Zorunlu | Varsayılan | Açıklama | +|------|-----|---------|------------|----------| +| `functionCache.ttlSeconds` | integer | Hayır | Host varsayılanı (**60 sn**) | Bu workflow'un built-in function yanıtları için cache TTL'i (saniye). `null` veya pozitif olmayan değer host varsayılanına düşer (`InstanceFunctionCache:DefaultTtlSeconds`) | + +Çalışma modeli: + +- Built-in function isteği cache'lenir; **aynı instance** için tekrarlanan istekler TTL boyunca cache'ten döner. +- **Instance değiştiğinde cache düşer** ve yeni istek yeniden cache'lenir. +- **State Function bu kapsamın dışındadır** — State Function cache'ini **platform kendisi yönetir** (host tarafındaki `StateFunctionCache` ayarları); `config.functionCache` onu etkilemez. + +### Resource Lock + +Transition tanımına eklenen `resourceLock` bloğu, paylaşılan bir kaynağı (koltuk, günlük limit, hesap vb.) birden fazla instance'ın aynı anda değiştirmesini engelleyen **dağıtık kilit** mekanizmasıdır (Dapr `lock.redis`). `start`, state-level ve `sharedTransitions` transition'larında geçerlidir ve yalnızca **Manual** profilde çalışır. Önerilen model, kilidi giriş transition'ında `Acquire` ile almak ve bırakmayı runtime'a devretmektir (instance terminal olduğunda otomatik release). Tam davranış modeli, `keyExpression` yazımı, conflict/409 ve örnekler için bkz. **[Kaynak Kilitleme (Resource Lock)](/docs/how-to/resource-lock)**. + ### MasterSchema `attributes.schema` alanı, workflow'un **instance data** ana yapısını belirler. Gelişmiş filtreleme ve instance data'nın her değişim noktasında **tutarlılık kontrolü** sağlar. diff --git a/docs/how-to/resource-lock.md b/docs/how-to/resource-lock.md new file mode 100644 index 0000000..39eea57 --- /dev/null +++ b/docs/how-to/resource-lock.md @@ -0,0 +1,194 @@ +--- +id: resource-lock +title: Kaynak Kilitleme (Resource Lock) +sidebar_label: Kaynak Kilitleme +description: Transition sırasında paylaşılan bir kaynağı dağıtık kilitle koruma +--- + +# Kaynak Kilitleme (Resource Lock) + +Resource Lock, bir transition çalışırken paylaşılan bir kaynağı (koltuk, zaman-slotu, günlük limit, hesap vb.) **birden fazla instance'ın aynı anda değiştirmesini** engelleyen dağıtık kilit mekanizmasıdır. Kilit, Aether SDK'nın Dapr distributed-lock building block'u (`lock.redis`) üzerinden yönetilir ve transition tanımına bağlı olarak **opt-in** çalışır. + +## Genel Bakış + +- Kilit, transition tanımındaki `resourceLock` bloğu ile **isteğe bağlı** olarak devreye girer. `resourceLock` tanımlı olmayan transition'lar bu adımı hiç çalıştırmaz. +- Pipeline'da **order 25** (`ResourceLockStep`) çalışır. Yalnızca **Manual** profilinde aktiftir; AutoChain / Scheduled / Event / ErrorBoundary profillerinde hariç tutulur. +- Kilit **sahibi (owner)** her zaman `instanceId`'dir. Yani bir kilit, onu alan instance'a aittir. +- Kilit anahtarı (`key`), her transition'da bir C# script'i (`ITransitionMapping`) çalıştırılarak runtime'da üretilir. +- **Kilit her zaman TTL'e sahiptir** — süresi dolunca otomatik serbest kalır. TTL, terkedilen kilitlere karşı nihai güvenlik ağıdır. + +### Çalışma modeli (önerilen) + +``` +Acquire (check transition) ──► ... iş adımları ... ──► Terminal state (Success/Error/Cancel) + │ │ + └─ kilit alınır, key instance'a kaydedilir └─ kilit OTOMATİK bırakılır +``` + +Doğru kullanım: kilidi **giriş (check) transition'ında `Acquire`** ile al; bırakmayı runtime'a devret. Instance terminal duruma (Completed / Faulted / Cancelled) ulaştığında kilit **otomatik olarak** serbest bırakılır — her terminal transition'a manuel `Release` koymana gerek yoktur (bkz. [Otomatik kilit temizliği](#otomatik-kilit-temizliği)). + +## Yapılandırma + +`resourceLock` bloğu bir transition tanımına eklenir: + +```json title="transition (resourceLock ile)" +{ + "key": "check-limit", + "from": "draft", + "target": "limit-reserved", + "triggerType": 0, + "versionStrategy": "Patch", + "resourceLock": { + "keyExpression": { + "location": "./src/LimitLockKey.csx", + "code": "", + "type": "L", + "encoding": "NAT" + }, + "action": "Acquire", + "ttlSeconds": 300, + "onConflict": "Abort" + } +} +``` + +| Alan | Tip | Zorunlu | Varsayılan | Açıklama | +|------|-----|---------|-----------|----------| +| `keyExpression` | ScriptCode (`ITransitionMapping`) | **Evet** | — | Kilit anahtarını üreten script. `Handler(ScriptContext)` bir string döndürmelidir. | +| `action` | `Acquire` \| `Release` \| `Extend` | **Evet** | — | Yapılacak kilit işlemi. | +| `ttlSeconds` | integer | Hayır | `300` | Acquire/Extend için kilidin yaşam süresi (saniye). Korunan işlemin tamamını kapsayacak şekilde boyutlandırılmalıdır. | +| `onConflict` | `Abort` | Hayır | `Abort` | Kilit alınamadığında politika. Şu an yalnızca `Abort` desteklenir. | + +### keyExpression — kilit anahtarı üretimi + +`keyExpression`, `ITransitionMapping` implemente eden bir C# script'idir. `Handler` metodu, `ScriptContext` üzerinden (`Headers`, `QueryParameters`, `Instance.Data`, `State`, `Transition`) kilit anahtarını hesaplar ve string olarak döndürür. + +```csharp title="keyExpression örneği (ITransitionMapping)" +public class Mapping : ITransitionMapping +{ + public async Task Handler(ScriptContext context) + { + // Aynı hesabın aynı işlem-günü için tekilliği garanti eden kilit anahtarı. + // ÖNEMLİ: tarihi UtcNow'dan YENİDEN hesaplama — instance verisinden oku (aşağıdaki uyarıya bak). + string account = context.Instance.Data.accountId; + string txnDate = context.Instance.Data.transactionDate; // kalıcı, akış boyunca değişmez + return $"limit:{account}:{txnDate}"; + } +} +``` + +:::warning Anahtar kararlılığı (gece-yarısı bug'ı) +Kilit anahtarını üretirken `DateTime.UtcNow` gibi **her çağrıda değişen** değerler kullanma. Aksi halde 23:59'da alınan kilidin anahtarı ile 00:00'da hesaplanan anahtar farklı olabilir. Anahtarı, akış başında verilere yazılmış **kalıcı bir değerden** (ör. `transactionDate`) türet. Otomatik release, alım anındaki anahtarı instance'a kaydettiği için bu sınıf hataya karşı ayrıca korumalıdır — ama yine de deterministik anahtar üret. +::: + +## Aksiyonlar + +### Acquire + +Kaynağı kilitler. Kilit başkasındaysa `ResourceLockConflict` hatası döner ve transition **abort** olur (bkz. [Conflict davranışı](#conflict--http-409)). Başarılı alımda anahtar, otomatik temizlik için instance'a kaydedilir. + +### Release + +Kilidi serbest bırakır. **Idempotent ve best-effort'tur** (HTTP DELETE gibi): + +- **Success** veya **LockDoesNotExist** (TTL dolmuş / hiç kilitlenmemiş) → başarı sayılır. "Bu kilidi benim üzerimden kaldır" son-koşulu zaten sağlanmıştır. +- **LockBelongsToOthers** / altyapı hatası → gerçek anomali; warning olarak loglanır (metrik için), fakat **transition'ı fault etmez**. + +Release **hiçbir durumda** başarılı bir iş transition'ını geri almaz. Kilit temizliği yüzünden doğru hesaplanmış bir işin fault olması engellenmiştir. + +:::tip +Otomatik terminal release sayesinde çoğu akışta explicit `Release` transition'ına ihtiyaç yoktur. Akışın ortasında erken bırakmak istersen `Release` kullanabilirsin; idempotent olduğu için otomatik release ile çakışması zararsızdır. +::: + +### Extend + +Mevcut kilidin TTL'ini uzatmaya çalışır. **Dikkat:** Dapr lock API'sinin native extend'i yoktur ve Redis bileşeni `SET NX` kullandığından, kilit hâlâ tutulurken (aynı sahip dahil) re-acquire reddedilir. Pratikte Extend, kilit **zaten TTL ile düşene kadar başarısız olur**; düştükten sonra "başarı" dönmesi aslında yeni bir yarış (race) alımıdır. Bu nedenle **Extend'e güvenme** — `ttlSeconds`'i korunan işlemin tamamını kapsayacak şekilde boyutlandır. + +## Otomatik kilit temizliği + +Bu, önerilen kullanım modelinin kalbidir. Bir instance **terminal** duruma ulaştığında (Completed / Faulted / Cancelled), o instance'ın tuttuğu tüm kilitler **otomatik olarak** bırakılır: + +1. `Acquire` başarılı olduğunda, çözülen anahtar instance metadata'sına (`ExtraProperties` → `resource.locks`, JSON array) kaydedilir. +2. Instance terminal olduğunda, ortak temizlik noktası (`InstanceCancellationService.ProcessCancellationAsync`) kaydedilmiş anahtarların her birini `owner=instanceId` ile serbest bırakır. + +Bunun sağladıkları: + +- **Terminal transition'ların her birine manuel `Release` koymaya gerek yok.** Yalnızca `Acquire` yeterlidir. +- **Fault/crash durumunda bile leak yok.** Instance beklenmedik şekilde fault etse (terminal transition çalışmasa) bile temizlik terminal statüde tetiklenir ve kilit TTL beklenmeden bırakılır. +- **En-az-bir-kez garanti.** Temizlik hem local hook (event publish anında) hem distributed endpoint (commit sonrası) yoluyla çalışır; release idempotent olduğu için çift çağrı zararsızdır. + +:::note +Kilit alımı ile instance'ın commit'i arasında (nadir) bir rollback olursa anahtar kalıcı olmayabilir; bu dar pencerede kilit **TTL** ile temizlenir — bu da explicit `Release`'in çalışmadığı senaryoyla aynı güvenlik ağıdır. +::: + +## Conflict → HTTP 409 + +`Acquire` sırasında kaynak başka bir instance tarafından tutuluyorsa: + +- `ResourceLockConflict` hatası üretilir → transition **abort** edilir. +- Kaybeden istek **HTTP 409 Conflict** alır. +- Instance faulted (`F`) olarak işaretlenir (DB'de kalıcı), ancak çağırana temiz bir 409 döner — çift-sayım (double-count) yarışı önlenmiş olur. + +İkinci eşzamanlı istek reddedilir; çağıran tarafın retry etmesi beklenir. (Şeffaf serileştirme / `Wait` politikası şu an desteklenmiyor; yalnızca `Abort`.) + +## Örnekler + +### Örnek 1 — Günlük limit rezervasyonu (önerilen model) + +```json title="check transition — sadece Acquire" +{ + "key": "reserve-daily-limit", + "from": "draft", + "target": "limit-reserved", + "triggerType": 0, + "versionStrategy": "Patch", + "resourceLock": { + "keyExpression": { "location": "./src/DailyLimitKey.csx", "code": "