diff --git a/TEST-SCENARIOS.md b/TEST-SCENARIOS.md index 52108e1..60ad24f 100644 --- a/TEST-SCENARIOS.md +++ b/TEST-SCENARIOS.md @@ -33,6 +33,7 @@ geçerliliğini yitiren senaryo **silinmez**, `deprecated` işaretlenip sebebi y | **soap-task-test** | `SoapTask` tipi · SOAP mapping (`SendVipSmsMapping.csx`) · başarı/hata rule'ları ile dallanma | SOAP task tipini uçtan uca doğrulamak 1 (2026-08-13) | — (yalnız `.http`) | — | ⚠️ Integration test yok — kapsam açığı | | **l1-cache-lab** | Component cache versiyon çözümü: `latest` + artifact/major range (`"1"`) referansları · generation-anahtarlı L1 (in-process) cache'in publish görünürlüğü · pinned instance `flowVersion` kararlılığı · publish-only aktivasyon (re-initialize'sız) · view/task referanslarının sıcak cache'de anında yeni versiyona dönmesi | Runtime'a eklenen L1 component cache'in (vnext `feature/component-cache-l1`, 2026-08-20) "versiyon cache'de kaldı" riskini uçtan uca çürütmek; CD sözleşmesinin (publish bitince yeni sürüm MUTLAKA geçerli) regresyon bekçisi | — (bilinçli: publish-akışı doğruluk senaryosu; api-test yeterli) | `api-tests/l1-cache-lab/l1-cache-behaviour-test.py` (`--minor N` ile tekrar koşulabilir) | ✅ Aktif — 18/18 (2026-08-20, lokal L1 runtime) | | **authorization-chain-lab** | `authorize` fonksiyonunun **aktif korelasyon zinciri** boyunca verdiği cevap: poll edilen instance'ın kendi `queryRoles`'u **VE** altındaki her seviye (en derin leaf'e kadar) — konjonksiyon · parent subflow override'larının **hop başına** ve **çocuğa damgalanmış** haritadan çözülmesi (A'nın B override'ı C'ye taşınmaz; B'nin C override'ı C'de uygulanır; override **REPLACE** eder, merge etmez) · doğrudan adreslenen leaf'in, parent üzerinden erişilen leaf ile **aynı** verdikti vermesi · `authorize`'ın dördüncü hedefi **`ack`** (long-poll acknowledge ön-kontrolü; `IsAwaitingLongPollAck` ile iner, bekleyen yoksa **allowed**) · aktif subflow varken `cancel`/`exit`/`updateData`/state'te müsait shared transition'ın **parent'ta** cevaplanması (execution ile aynı) · `data`'nın içeriğinin inmemesi ama yetkilendirmesinin inmesi · runtime'ın kendi `queryRoles` ve ack **kapılarının kaldırılmış olması** (on yüzeyin hiçbiri 403 dönmüyor ve hiçbiri kapıya sormuyor), buna karşılık `authorize`'ın **reddetmeyi sürdürmesi** — o, gateway'in danıştığı kâhin, runtime'ın yolundaki bir kapı değil — ve rol **çözümlemesinin** (availableTransitions filtresi, state alias, `x-roles` budama, human-task listesi, `CallerScopeHash`) aynen sürmesi | Council `2026-09-22-remove-execution-authorization`: `authorize` üç canlı kusur taşıyordu ve üçü de aynı sonuca çıkıyordu — **ara katmanın danıştığı cevap, runtime'ın uyguladığı kapıdan farklıydı**. (1) `?queryRoles=true` aktif subflow'a kısa devre yapıp kökün kendi grant'larını hiç değerlendirmiyordu, yani tarif ettiği kapıdan **zayıftı**; (2) parent override'ı eşleşince iniş derinlik 1'de duruyor, torunun kapısı hiç çalışmıyordu; (3) override parent'ın TANIMINDAN okunduğu için doğrudan adreslenen leaf'te hiçbir şey bulamıyor ve `authorize` ile state function aynı instance için **zıt** verdikt veriyordu. Ayrıca ara katmanın long-poll ack'i soramaması. Enforcement önce bir geçiş anahtarına alındı, sonra kullanıcı kararıyla kapılar **tamamen kaldırıldı** (2026-09-23): `queryRoles` silinmedi, yeri değişti — tam ve hop başına, `authorize?queryRoles=true` içinde değerlendirilmeye devam ediyor; silinen şey aynı kararın runtime'daki **ikinci** kopyası | `Tests/AuthorizationChainLab` (7 sınıf, 44 + 5 provider testi) + `Tests/AuthorizationChainLab/README.md` + MockLab seed `morph-idm-roles-collection.json` | — (bilinçli: doğruluk senaryosu, eşzamanlılık/gecikme iddiası yok) | ✅ **44/44 yeşil; ayrıca `morph-idm` provider'ıyla 7/7** (2026-09-23, runtime lokal build `f7a053c8` + `claude/remove-authorize-checks-cc40e5`, `VNEXT_BASE_URL=http://localhost:4201`). Suite önce geçiş anahtarına karşı yazılıp iki duruşta da koşuldu; kapılar kaldırıldıktan sonra anahtarı ölçen testler kaldırmayı ölçenlere dönüştürülüp yeniden koşuldu (ortam değişkeni kalmadı). **İlk koşuda üç runtime kusuru bulundu ve üçü de düzeltildi**: (1) `queryRoles` kapısı `EffectiveState` okuyordu — kendi subflow'u olan bir ara seviyede bu bir TORUNUN state anahtarıdır, parent'ın workflow'u onu çözemez, damgalı override eşleşemez ve kapı sessizce workflow kökünün grant'larına düşerdi (`chain.mid-only`, kök onu `chain.admin`'e daraltmışken mid'i 200 okudu); (2) state transition'ları `availableIn` üzerinden okunuyordu — bu liste onlarda boştur ve "boş = her state" kuralı `approve`'u `intake`'te de allowed gösteriyordu, yani oracle **permissive** yönde yanlıştı; (3) `interaction` bloğu state'in BEYANINA göre yayınlanıyordu, gerçekten ack beklenip beklenmediğine göre değil (`ResponseShapeVersion` v10→v11). `morph-idm` provider'ı için suite'in tamamı DEĞİL, yalnız `MorphIdmProviderTests` koşar (7 test, üçüncü bir başlatma + MockLab seed'i): konjonksiyon ve override'lar provider'dan bağımsızdır — bir rol kümesini tüketirler, nereden geldiğine karar vermezler — provider'a özgü olan hangi kümenin geldiğidir. Kanıtlanan: `role`/`x-roles` header'ı morph-idm `204` dediğinde **hayatta kalmaz** (header fallback yok), aynı şey `role` **query parametresi** için de geçerlidir (bu açık bu koşuda prob atılarak bulundu ve `ICallerRoleResolver.AllowsRoleParameterFallback` ile kapatıldı — ölçülen: `?queryRoles=true` 403 iken `?queryRoles=true&role=chain.admin` 200 dönüyordu), hiç role header'ı olmadan servisin cevabı yetki verir, 5xx boş küme değil **403** üretir, ve okuma yüzeyleri `authorize` ile **aynı** provider'dan rol çözer (kapı kalkınca 403 önermesi düştü; yerine teklif edilen transition kümesinin provider'ın cevabına göre değişmesi ölçülüyor). **Bilinen kapsam açığı:** `interaction.longPoll.rule` kolu **authorlanamıyor** — rule kolu runtime'a #936 ile girdi ama vnext-schema (kurulu 0.0.52 ve sibling 1.0.0) `required: [terminate, roles]` + `additionalProperties: false` diyor; `vnext-meta/features.json`'ın "şema roles|rule tekini zorlar" iddiası **yanlış**. Kolun kendisi `LongPollInteractionGateTests` ile unit seviyede pinli ve `authorize?ack=true` aynı gate'e delege ettiği için ara katman onu sorabiliyor | +| **subflow-override-lab** | Parent'ın SubFlow çocuğuna verdiği **long-poll ve kapsamlı view override'ları**: `overrides.states..interaction.longPoll.fallbackTimeoutSeconds` / `.roles` (alan düzeyinde; yazılmayan alan çocuğunkini korur, `roles` bütün olarak değişir, long-poll'u olmayan state'e long-poll **eklenmez** — 20305) · state gövdesinin `interaction.fallbackTimeoutSeconds`'ı, arm edilen fallback job'u ve `authorize?ack=true`'nun **aynı** etkin değeri görmesi · arm'ın da override'lı rolleri okuması · `overrides.states..views` / `overrides.transitions..views` (çocuğun kendi kuralının seçtiği view'un referansı; state↔transition birbirine düşmez; çözülemeyen override → çocuğun view'u, 20101) · çözümün **çocuk tarafında** ve kendi `CurrentState`'inde olması: doğrudan adreslenen çocukta da geçerli, **tek hop** (P'nin override'ı torunu etkilemez) · eski `overrides.views` haritasının parent-taraflı davranışının sürmesi | Runtime `feature/subflow-override-interaction-views` (spec 2026-09-23): pencere çocuğun pipeline'ında (order 75) tüketiliyor ve parent'ın onu ayarlamasının yolu yoktu; düz view-key haritası bir view'u her yerde birden değiştiriyordu. `authorization-chain-lab`'i genişletmek o suite'in ack sözleşmesini (leaf `roles=[chain.admin]`) değiştirirdi | `Tests/SubflowOverrideLab` (2 sınıf, 12 test) + `Tests/SubflowOverrideLab/README.md` | — (bilinçli: doğruluk senaryosu) | ✅ **12/12** (2026-09-23, iki koşu, runtime lokal build `feature/subflow-override-interaction-views` @ `e7cb023c`, `VNEXT_BASE_URL=http://localhost:4201`); komşular aynı runtime'da AuthorizationChainLab 44/44, SubflowOrchestration 22/22, TimeoutLab 3/3. Pencere davranışsal ölçülüyor: işlenen job `ModifiedAt−CreatedAt` = 120.2 s (override 120, çocuk 600) ve 5.1 s (override 5). O koşuda long-poll ack `InstanceJobs.ExecuteAt` NULL'dı; runtime `5f60ab4b` ile artık yazılıyor (postgres: düzeltme öncesi 22 satırda 0, sonrası 10/10; `ExecuteAt−CreatedAt` = 600/120/5 s, suite yeniden 12/12). Okuma yüzeyi olmadığı ve suite'in DB erişimi olmadığı için test davranışsal kalır. **Bilinen kapsam açığı:** kurulu vnext-schema 0.0.52 yeni override alanlarını tanımıyor — beş parent akışı `npm run validate`'ten düşer; SDK doğrudan publish ettiği için test engeli değil, şema değişikliği yayınlanmadı. Lab'ın SubFlow mapping'i rol header'larını çocuğa iletir: arm yalnız tetikleyen çağıran etkin rolleri karşılıyorsa durur ve `SubflowStarter` yalnız mapping'in döndürdüğü header'ları gönderir | | **role-matrix-lab** | Yetkilendirme yüzeylerinin tutarlılığı: root vs state `queryRoles` (state EZER) · `transition.roles` allowlist / blacklist / predefined (`$InstanceStarter`) · `availableIn` rol daraltması (**AND**) · well-known transition'ların (`cancel`/`updateData`/`exit`) configured key + `kind` ile listelenmesi · master şemada `x-roles` alan budaması · `authorize` function'ının üç hedefi (transitionKey / functionKey / queryRoles) · custom function'da rol denetiminin **kaldırılmış** olması | Provider bazlı caller-role çözümü (`default` \| `morph-idm`) + custom function rol gate'inin kaldırılması; rol setinin KAYNAĞI değişirken grant motorunun davranışının değişmediğini pinlemek (2026-08-19, `feature/caller-role-provider`) | `Tests/RoleMatrixLab` (5 sınıf, 59 test) | — (bilinçli: doğruluk senaryosu, eşzamanlılık değil) | 🆕 Yazıldı, henüz koşulmadı | | **secret-cache-lab** | `ScriptBase.GetSecretAsync` üzerinden in-process secret bundle cache (`ScriptSecretCache`) · bundle başına tek Vault fetch (single-flight) · cache'in request'ler arası (process-wide) yaşaması · TTL süresince bilinçli staleness · TTL dolunca canlı değere tazelenme · script task (type 7) içinden secret erişimi | Script secret fonksiyonlarının her çağrıda vault'a gitmesi yük altında vault'u darboğaza sokuyordu; `Scripting:SecretCache` (TTL 30 sn) geliştirmesinin hem kazancını hem de bayatlık sınırını uçtan uca pinlemek (vnext `claude/scriptbase-secret-cache-y86e03`, 2026-08-20) | — (bilinçli: doğrulama Vault audit log'u + saat ölçümüne dayanıyor, SDK assertion yüzeyinde yok) | `api-tests/secret-cache-lab/secret-cache-behaviour-test.py` | ✅ Aktif — 12/12 (2026-08-20, lokal runtime, TTL 30 sn) | | **fan-out-documents** | `FanOutTask` (TaskType 21) inline mode · `itemsPath` ile koleksiyon çözümü + `ItemKey` türetimi (`id` → `key` → index) · `IFanOutMapping.ItemInputHandler` ile klonlanmış iç task'ın per-item mutasyonu (HTTP url) · `allSettled` join politikası ve `{resultKey}Summary{total,succeeded,failed,timedOut}` üzerinden auto transition dallanması (`RunAutomaticTransitionsStep`, order 90) · **tek-yazim degismezi**: N item → 1 InstanceData sürümü (`SuppressDataApply` + atılan branch context, tek yazım noktası batch'in çıktı adımı) · **`IFanOutMapping.OutputHandler` geri-düşüşü**: mapping yalnız `ItemInputHandler`'ı override eder, çıktıyı runtime'ın `BuildDefaultOutput`'u üretir · iki seviyeli bulkhead (batch-yerel `maxDegreeOfParallelism` × süreç geneli `Workflow:FanOut:MaxConcurrentItems`) · item journal anahtarları `{fanOutTaskKey}#{index}` · sonuçların `join.ordered`'dan bağımsız olarak her zaman index sıralı dönmesi | Runtime'a eklenen FanOutTask'ın (vnext `feature/fanout-task-design`, 2026-08-21) **tek-yazim degismezini** regresyona karşı sabitlemek: bastırma bozulursa fan-out tek bir aggregate üzerinde yarışan N eş zamanlı yazıcıya döner ve tasarımın var oluş sebebi kaybolur. İkincil olarak `allSettled` + özet + auto transition kısmi-başarısızlık kalıbının çalışır bir örneğini pinler (2026-08-21) | `Tests/FanOut` (`FanOutDocumentsTests`, 4 test) | `api-tests/fan-out-documents/fanout-load.py` (bulkhead tavanı + yük altında tek-yazım + straggler oranı) | ✅ 4/4 yeşil (2026-08-22, `ad72158b`) + yük testi PASS: kuyruksuz profil 6/6 (`--instances 4 --items 3 --max-dop 3`), doygun varsayılan profil 5/5 (BULKHEAD bilinçli SKIP — aşağıya bakın). `npm run validate` TaskType 21'i hâlâ reddediyor (`@burgan-tech/vnext-schema@0.0.52` enum'u `"20"`de bitiyor; şema paketi release bekliyor) — publish yolu validate'ten geçmediği için engel değil: SDK `LocalDomainPublisher` component JSON'ını doğrudan `POST /api/v1/definitions/publish`'e atıyor. Item journal assertion'ı **bilinçli olarak yok**: satırlar yalnız monitoring host'unda (4203) görünüyor, SDK stack'i onu başlatmıyor — `fanout-load.py --monitor-url` ile opt-in. **2026-08-22 düzeltmesi:** `DOC-SLOW` straggler route'u `api/fan-out/documents/process-slow` adresindeydi ve MockLab route'ları **PREFIX** ile eşlediği için `documents/process` mock'u tarafından yutuluyordu — yani gecikme hiç uygulanmıyordu ve `fanout-load.py`'nin **straggler oranı metriği jitter ölçüyordu**. Route `api/fan-out/slow-documents/process`'e taşındı, mapping güncellendi, akış 1.0.2'ye bump edildi (integration testler 4/4 yeşil kaldı). Yük testi bundan sonra ilk kez anlamlı sayı üretti ve **iki metrik hatası ortaya çıktı, ikisi de düzeltildi**: (1) `BULKHEAD` metriği doygunlukta **geçersiz** — `sum(durationMs)/wall` "uçuşta geçen süre" varsayıyor ama `FanOutTaskExecutor` item stopwatch'ini slot beklemelerinden **önce** başlattığı için `durationMs` kuyruk süresini de içeriyor (runtime bu yüzden span'e ayrıca `vnext.fanout.item.queue_wait_ms` basıyor); iddia artık yalnızca kuyruksuz profilde kuruluyor (`items <= max-dop` **ve** `instances*items <= ceiling`), aksi halde sebebiyle SKIP. (2) `STRAGGLER` eşiği (`<= 4.0`) yutulmuş route'a kalibreliydi; gerçek straggler ile oran **tasarım gereği ~10**. Eşik 15.0'a çıkarıldı ve **iki taraflı** yapıldı: yeni `STRAGGLER-VAR` tabanı **mutlak** (`en yavaş item >= 1200ms`), çünkü `max/p50` oranı presence detektörü olarak gürültülü — hiç `DOC-SLOW` yokken bile 9.44 üretti | diff --git a/core/Views/subflow-override-lab/subflow-override-lab-child-confirm-view.json b/core/Views/subflow-override-lab/subflow-override-lab-child-confirm-view.json new file mode 100644 index 0000000..84736af --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-child-confirm-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-child-confirm-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "child confirm view", + "tr": "child confirm view" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Views/subflow-override-lab/subflow-override-lab-child-lp-view.json b/core/Views/subflow-override-lab/subflow-override-lab-child-lp-view.json new file mode 100644 index 0000000..1a3693d --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-child-lp-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-child-lp-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "child long-poll view", + "tr": "child long-poll view" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Views/subflow-override-lab/subflow-override-lab-child-plain-view.json b/core/Views/subflow-override-lab/subflow-override-lab-child-plain-view.json new file mode 100644 index 0000000..7303258 --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-child-plain-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-child-plain-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "child plain view", + "tr": "child plain view" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Views/subflow-override-lab/subflow-override-lab-legacy-view.json b/core/Views/subflow-override-lab/subflow-override-lab-legacy-view.json new file mode 100644 index 0000000..2e32ca0 --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-legacy-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-legacy-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "legacy parent-side override", + "tr": "legacy parent-side override" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Views/subflow-override-lab/subflow-override-lab-parent-confirm-view.json b/core/Views/subflow-override-lab/subflow-override-lab-parent-confirm-view.json new file mode 100644 index 0000000..f5742a0 --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-parent-confirm-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-parent-confirm-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "parent transition-scoped override", + "tr": "parent transition-scoped override" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Views/subflow-override-lab/subflow-override-lab-parent-lp-view.json b/core/Views/subflow-override-lab/subflow-override-lab-parent-lp-view.json new file mode 100644 index 0000000..38eb15a --- /dev/null +++ b/core/Views/subflow-override-lab/subflow-override-lab-parent-lp-view.json @@ -0,0 +1,28 @@ +{ + "key": "subflow-override-lab-parent-lp-view", + "version": "1.0.0", + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": [ + "integration-test", + "subflow-override-lab", + "pseudo-ui" + ], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": { + "type": "Text", + "content": { + "en": "parent state-scoped override", + "tr": "parent state-scoped override" + }, + "variant": "headlineMedium" + } + } + } +} diff --git a/core/Workflows/subflow-override-lab/build-subflow-override-lab.py b/core/Workflows/subflow-override-lab/build-subflow-override-lab.py new file mode 100644 index 0000000..b7b6758 --- /dev/null +++ b/core/Workflows/subflow-override-lab/build-subflow-override-lab.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +""" +subflow-override-lab akislarini ve view'larini uretir. + + python3 core/Workflows/subflow-override-lab/build-subflow-override-lab.py + +Bu lab TEK BIR SEYI olcer: bir parent'in `state.subFlow.overrides` ile, tukettigi COCUGUN +state'lerine verdigi iki yeni override'in (runtime 2026-09-23, branch +feature/subflow-override-interaction-views) uctan uca davranisini: + + * `overrides.states..interaction.longPoll.{fallbackTimeoutSeconds, roles}` + — alan duzeyinde: yazilmayan alan cocugunkini korur; `roles` listesi butun olarak degisir; + override long-poll'u OLMAYAN bir state'e long-poll EKLEMEZ. + * `overrides.states..views.` ve + `overrides.transitions..views.` — cocugun KENDI kurallarinin sectigi + view'un referansini degistirir; state ve transition birbirine dusmez. + +Override'lar cocuga baslangicta damgalanir ve COCUK tarafinda, cocugun kendi CurrentState'i +uzerinden cozulur — bu yuzden cocuk dogrudan adreslendiginde de gecerlidir ve tek hop'tur. + +Akislar +------- + CHILD (S) child-initial --auto--> lp-wait + lp-wait : interaction.longPoll {terminate, fallback 600, roles [ovr.child-ack]} + view child-lp-view + transitions: confirm (view child-confirm-view), note (view child-lp-view) + PLAIN-CHILD (S) plain-initial --auto--> plain-wait (long-poll YOK, view child-plain-view) + + PARENT (F) waiting -> CHILD overrides.states.lp-wait: + interaction.longPoll.fallbackTimeoutSeconds = 120 (yalniz sure) + views { child-lp-view -> parent-lp-view } + overrides.transitions.confirm.views + { child-confirm-view -> parent-confirm-view } + PARENT-SHORT (F) waiting -> CHILD overrides.states.lp-wait.interaction.longPoll.fallback = 5 + (pencere davranissal olarak olculur: cocuk ~5 s'de devam etmeli) + PARENT-ROLES (F) waiting -> CHILD overrides.states.lp-wait: + interaction.longPoll.roles = [ovr.parent-ack] (yalniz roller) + views { child-lp-view -> } (D10: geri dusus) + PARENT-NOLP (F) waiting -> PLAIN-CHILD + overrides.states.plain-wait.interaction.longPoll.fallback = 90 + (cocukta long-poll yok -> yok sayilir, EventId 20305) + PARENT-LEGACY (F) waiting -> CHILD overrides.views { child-lp-view -> legacy-view } + (eski, parent-tarafli harita — regresyon kontrolu) + TOP (F) waiting -> MID (S) mid-wait -> CHILD + TOP'un override'i `lp-wait` anahtarini adlandirir — o anahtar + MID'de YOK, torunda (CHILD) var. Tek hop: torunu etkilememeli. + MID kendi override'ini bildirmez. + +Neden ayri bir akis: authorization-chain-lab'in leaf'i zaten bir long-poll tasiyor ve +AckAndParentRetainedTests onun `roles = [chain.admin]` oldugunu varsayiyor; pencereyi/rolleri +override etmek o suite'in ack sozlesmesini degistirirdi. + +Sema acigi: kurulu @burgan-tech/vnext-schema `subFlowStateOverride.interaction`/`views` ve +`subFlowTransitionOverride.views` alanlarini tanimiyor (`additionalProperties: false`), yani bu +akislar `npm run validate`'ten gecmez. SDK'nin LocalDomainPublisher'i JSON'u dogrudan +/definitions/publish'e gonderdigi icin test icin engel degil; sema degisikligi yerel bir +vnext-schema dalinda, yayinlanmadi. +""" + +import base64 +import json +import pathlib + +HERE = pathlib.Path(__file__).resolve().parent +SRC = HERE / "src" +SRC.mkdir(exist_ok=True) +VIEWS = HERE.parents[1] / "Views" / "subflow-override-lab" +VIEWS.mkdir(parents=True, exist_ok=True) + +# Publish is VERSION-IMMUTABLE (409 Instance:100002): bump on every fixture change. +FIXTURE_VERSION = "1.0.2" +VIEW_VERSION = "1.0.0" + +CHILD_ACK = "ovr.child-ack" +PARENT_ACK = "ovr.parent-ack" + +CHILD = "subflow-override-lab-child" +PLAIN_CHILD = "subflow-override-lab-plain-child" +MID = "subflow-override-lab-mid" + +CHILD_LP_VIEW = "subflow-override-lab-child-lp-view" +CHILD_CONFIRM_VIEW = "subflow-override-lab-child-confirm-view" +CHILD_PLAIN_VIEW = "subflow-override-lab-child-plain-view" +PARENT_LP_VIEW = "subflow-override-lab-parent-lp-view" +PARENT_CONFIRM_VIEW = "subflow-override-lab-parent-confirm-view" +LEGACY_VIEW = "subflow-override-lab-legacy-view" +MISSING_VIEW = "subflow-override-lab-missing-view" # never published — D10 fallback probe + + +def grants(*roles): + return [{"role": r, "grant": "allow"} for r in roles] + + +def label(text): + return [{"language": "en-US", "label": text}] + + +def view_ref(key): + return {"key": key, "domain": "core", "version": VIEW_VERSION, "flow": "sys-views"} + + +def view_def(key): + return {"view": view_ref(key), "loadData": False} + + +def csx(name: str, body: str) -> dict: + path = SRC / f"{name}.csx" + path.write_text(body, encoding="utf-8") + return {"location": f"./src/{name}.csx", "code": base64.b64encode(body.encode("utf-8")).decode("ascii")} + + +SUBFLOW_MAPPING = """using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Threading.Tasks; +using BBT.Workflow.Scripting; + +// Pass-through for data. It DOES forward the caller's role headers: SubflowStarter sends the child +// only the headers the input mapping returns, and the long-poll arm (order 75) pauses only when the +// TRIGGERING caller satisfies the effective long-poll roles. Without this forward a roles-armed +// long-poll in a SubFlow child never pauses, and nothing about the override would be observable. +public class OverrideLabSubFlowMapping : ScriptBase, ISubFlowMapping +{ + public Task InputHandler(ScriptContext context) + { + dynamic input = new ExpandoObject(); + input.startedByOverrideLab = true; + + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var name in new[] { "x-roles", "role", "user_reference", "x-device-id", "x-token-id" }) + { + try + { + string? value = (string?)context.Headers[name]; + if (!string.IsNullOrEmpty(value)) headers[name] = value; + } + catch (Exception) + { + // header absent + } + } + + return Task.FromResult(new ScriptResponse { Data = input, Headers = headers }); + } + + public Task OutputHandler(ScriptContext context) + => Task.FromResult(new ScriptResponse()); +} +""" + +ALWAYS_TRUE = """using System.Threading.Tasks; +using BBT.Workflow.Scripting; + +// Each flow walks itself to its waiting state so a test only starts the top. +public class OverrideLabAlwaysTrueRule : ScriptBase, IConditionMapping +{ + public Task Handler(ScriptContext context) => Task.FromResult(true); +} +""" + + +def envelope(key, attributes, *tags): + return { + "key": key, + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": FIXTURE_VERSION, + "tags": ["integration-test", "subflow-override-lab", *tags], + "attributes": attributes, + } + + +def initial_state(prefix, target): + return { + "key": f"{prefix}-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": label(f"{prefix} initial"), + "view": None, + "subFlow": None, + "transitions": [{ + "key": f"auto-{prefix}-to-{target}", + "target": target, + "triggerType": 1, + "versionStrategy": "Minor", + "labels": label("Auto"), + "rule": csx("OverrideLabAlwaysTrueRule", ALWAYS_TRUE), + }], + } + + +def finish_states(prefix): + return [ + {"key": f"{prefix}-done", "stateType": 3, "subType": 1, "versionStrategy": "Major", + "labels": label(f"{prefix} done"), "view": None, "subFlow": None, "transitions": []}, + {"key": f"{prefix}-cancelled", "stateType": 3, "subType": 7, "versionStrategy": "Major", + "labels": label(f"{prefix} cancelled"), "view": None, "subFlow": None, "transitions": []}, + ] + + +def subflow_state(key, child_key, overrides=None): + state = { + "key": key, + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": label(key), + "view": None, + "transitions": [], + "subFlow": { + "type": "S", + "process": {"key": child_key, "domain": "core", "version": FIXTURE_VERSION, "flow": "sys-flows"}, + "mapping": csx("OverrideLabSubFlowMapping", SUBFLOW_MAPPING), + }, + } + if overrides is not None: + state["subFlow"]["overrides"] = overrides + return state + + +def cancel(prefix): + return { + "cancel": {"key": f"cancel-{prefix}", "target": f"{prefix}-cancelled", "triggerType": 0, + "versionStrategy": "Major", "labels": label("Cancel")}, + } + + +def build_child(): + attributes = { + "type": "S", + "timeout": None, + "labels": label("SubFlow Override Lab Child"), + "functions": [], "features": [], "extensions": [], "sharedTransitions": [], + "startTransition": {"key": f"start-{CHILD}", "target": "child-initial", "triggerType": 0, + "versionStrategy": "Major", "labels": label("Start Child")}, + "states": [ + initial_state("child", "lp-wait"), + { + "key": "lp-wait", + "stateType": 2, + "subType": 6, + "versionStrategy": "Major", + "labels": label("Long-poll Wait"), + "view": view_def(CHILD_LP_VIEW), + "subFlow": None, + # 600 s: long enough that the fallback never fires inside a test run, and far from + # every override value below so a leaked value is unmistakable. + "interaction": {"longPoll": {"terminate": True, "fallbackTimeoutSeconds": 600, + "roles": grants(CHILD_ACK)}}, + "transitions": [ + {"key": "confirm", "target": "child-done", "triggerType": 0, "versionStrategy": "Minor", + "labels": label("Confirm"), "view": view_def(CHILD_CONFIRM_VIEW)}, + # Same view KEY as the state view, no transition override: the state override + # must not leak into a transition view that happens to share the key. + {"key": "note", "target": "$self", "triggerType": 0, "versionStrategy": "Minor", + "labels": label("Note"), "view": view_def(CHILD_LP_VIEW)}, + ], + }, + *finish_states("child"), + ], + } + return envelope(CHILD, attributes, "child") + + +def build_plain_child(): + attributes = { + "type": "S", + "timeout": None, + "labels": label("SubFlow Override Lab Plain Child"), + "functions": [], "features": [], "extensions": [], "sharedTransitions": [], + "startTransition": {"key": f"start-{PLAIN_CHILD}", "target": "plain-initial", "triggerType": 0, + "versionStrategy": "Major", "labels": label("Start Plain Child")}, + "states": [ + initial_state("plain", "plain-wait"), + { + "key": "plain-wait", + "stateType": 2, + "subType": 6, + "versionStrategy": "Major", + "labels": label("Plain Wait (no long-poll)"), + "view": view_def(CHILD_PLAIN_VIEW), + "subFlow": None, + "transitions": [ + {"key": "finish-plain", "target": "plain-done", "triggerType": 0, + "versionStrategy": "Minor", "labels": label("Finish")}, + ], + }, + *finish_states("plain"), + ], + } + return envelope(PLAIN_CHILD, attributes, "plain-child") + + +def build_mid(): + attributes = { + "type": "S", + "timeout": None, + "labels": label("SubFlow Override Lab Mid"), + "functions": [], "features": [], "extensions": [], "sharedTransitions": [], + "startTransition": {"key": f"start-{MID}", "target": "mid-initial", "triggerType": 0, + "versionStrategy": "Major", "labels": label("Start Mid")}, + "states": [ + initial_state("mid", "mid-wait"), + # No overrides of its own: whatever reaches the grandchild could only have come from TOP. + subflow_state("mid-wait", CHILD), + *finish_states("mid"), + ], + } + return envelope(MID, attributes, "mid") + + +def build_parent(key, child, overrides, tag): + attributes = { + "type": "F", + "timeout": None, + "labels": label(f"SubFlow Override Lab {tag}"), + "functions": [], "features": [], "extensions": [], "sharedTransitions": [], + **cancel("parent"), + "startTransition": {"key": f"start-{key}", "target": "parent-initial", "triggerType": 0, + "versionStrategy": "Major", "labels": label("Start")}, + "states": [ + initial_state("parent", "waiting"), + subflow_state("waiting", child, overrides), + *finish_states("parent"), + ], + } + return envelope(key, attributes, tag) + + +def build_view(key, text): + return { + "key": key, + "version": VIEW_VERSION, + "domain": "core", + "flow": "sys-views", + "flowVersion": "1.0.0", + "tags": ["integration-test", "subflow-override-lab", "pseudo-ui"], + "attributes": { + "type": 1, + "display": "full-page", + "renderer": "pseudo-ui", + "content": { + "$schema": "https://amorphie.io/meta/view-vocabulary/1.0", + "view": {"type": "Text", "content": {"en": text, "tr": text}, "variant": "headlineMedium"}, + }, + }, + } + + +def write(doc, folder=HERE): + path = folder / f"{doc['key']}.json" + path.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + print("wrote", path.relative_to(HERE.parents[2])) + + +def main(): + for key, text in [ + (CHILD_LP_VIEW, "child long-poll view"), + (CHILD_CONFIRM_VIEW, "child confirm view"), + (CHILD_PLAIN_VIEW, "child plain view"), + (PARENT_LP_VIEW, "parent state-scoped override"), + (PARENT_CONFIRM_VIEW, "parent transition-scoped override"), + (LEGACY_VIEW, "legacy parent-side override"), + ]: + write(build_view(key, text), VIEWS) + + write(build_child()) + write(build_plain_child()) + write(build_mid()) + + write(build_parent("subflow-override-lab-parent", CHILD, { + "states": {"lp-wait": { + "interaction": {"longPoll": {"fallbackTimeoutSeconds": 120}}, + "views": {CHILD_LP_VIEW: view_ref(PARENT_LP_VIEW)}, + }}, + "transitions": {"confirm": {"views": {CHILD_CONFIRM_VIEW: view_ref(PARENT_CONFIRM_VIEW)}}}, + }, "parent")) + + # The window is not persisted anywhere readable (see README: the long-poll ack InstanceJobs row + # carries no ExecuteAt, and the Dapr job is scheduled directly). So the duration override is + # proven BEHAVIOURALLY: a 5 s override on a 600 s child must resume the child within seconds. + write(build_parent("subflow-override-lab-parent-short", CHILD, { + "states": {"lp-wait": {"interaction": {"longPoll": {"fallbackTimeoutSeconds": 5}}}}, + }, "parent-short")) + + write(build_parent("subflow-override-lab-parent-roles", CHILD, { + "states": {"lp-wait": { + "interaction": {"longPoll": {"roles": grants(PARENT_ACK)}}, + "views": {CHILD_LP_VIEW: view_ref(MISSING_VIEW)}, + }}, + }, "parent-roles")) + + write(build_parent("subflow-override-lab-parent-nolp", PLAIN_CHILD, { + "states": {"plain-wait": {"interaction": {"longPoll": {"fallbackTimeoutSeconds": 90}}}}, + }, "parent-nolp")) + + write(build_parent("subflow-override-lab-parent-legacy", CHILD, { + "views": {CHILD_LP_VIEW: view_ref(LEGACY_VIEW)}, + }, "parent-legacy")) + + write(build_parent("subflow-override-lab-top", MID, { + # Names a GRANDCHILD state. One hop: must not reach it. + "states": {"lp-wait": { + "interaction": {"longPoll": {"fallbackTimeoutSeconds": 45, "roles": grants(PARENT_ACK)}}, + "views": {CHILD_LP_VIEW: view_ref(PARENT_LP_VIEW)}, + }}, + }, "top")) + + +if __name__ == "__main__": + main() diff --git a/core/Workflows/subflow-override-lab/src/OverrideLabAlwaysTrueRule.csx b/core/Workflows/subflow-override-lab/src/OverrideLabAlwaysTrueRule.csx new file mode 100644 index 0000000..9a68b03 --- /dev/null +++ b/core/Workflows/subflow-override-lab/src/OverrideLabAlwaysTrueRule.csx @@ -0,0 +1,8 @@ +using System.Threading.Tasks; +using BBT.Workflow.Scripting; + +// Each flow walks itself to its waiting state so a test only starts the top. +public class OverrideLabAlwaysTrueRule : ScriptBase, IConditionMapping +{ + public Task Handler(ScriptContext context) => Task.FromResult(true); +} diff --git a/core/Workflows/subflow-override-lab/src/OverrideLabSubFlowMapping.csx b/core/Workflows/subflow-override-lab/src/OverrideLabSubFlowMapping.csx new file mode 100644 index 0000000..b50416f --- /dev/null +++ b/core/Workflows/subflow-override-lab/src/OverrideLabSubFlowMapping.csx @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Threading.Tasks; +using BBT.Workflow.Scripting; + +// Pass-through for data. It DOES forward the caller's role headers: SubflowStarter sends the child +// only the headers the input mapping returns, and the long-poll arm (order 75) pauses only when the +// TRIGGERING caller satisfies the effective long-poll roles. Without this forward a roles-armed +// long-poll in a SubFlow child never pauses, and nothing about the override would be observable. +public class OverrideLabSubFlowMapping : ScriptBase, ISubFlowMapping +{ + public Task InputHandler(ScriptContext context) + { + dynamic input = new ExpandoObject(); + input.startedByOverrideLab = true; + + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var name in new[] { "x-roles", "role", "user_reference", "x-device-id", "x-token-id" }) + { + try + { + string? value = (string?)context.Headers[name]; + if (!string.IsNullOrEmpty(value)) headers[name] = value; + } + catch (Exception) + { + // header absent + } + } + + return Task.FromResult(new ScriptResponse { Data = input, Headers = headers }); + } + + public Task OutputHandler(ScriptContext context) + => Task.FromResult(new ScriptResponse()); +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-child.json b/core/Workflows/subflow-override-lab/subflow-override-lab-child.json new file mode 100644 index 0000000..a71c474 --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-child.json @@ -0,0 +1,180 @@ +{ + "key": "subflow-override-lab-child", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "child" + ], + "attributes": { + "type": "S", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab Child" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "startTransition": { + "key": "start-subflow-override-lab-child", + "target": "child-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start Child" + } + ] + }, + "states": [ + { + "key": "child-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "child initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-child-to-lp-wait", + "target": "lp-wait", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "lp-wait", + "stateType": 2, + "subType": 6, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Long-poll Wait" + } + ], + "view": { + "view": { + "key": "subflow-override-lab-child-lp-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + }, + "loadData": false + }, + "subFlow": null, + "interaction": { + "longPoll": { + "terminate": true, + "fallbackTimeoutSeconds": 600, + "roles": [ + { + "role": "ovr.child-ack", + "grant": "allow" + } + ] + } + }, + "transitions": [ + { + "key": "confirm", + "target": "child-done", + "triggerType": 0, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Confirm" + } + ], + "view": { + "view": { + "key": "subflow-override-lab-child-confirm-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + }, + "loadData": false + } + }, + { + "key": "note", + "target": "$self", + "triggerType": 0, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Note" + } + ], + "view": { + "view": { + "key": "subflow-override-lab-child-lp-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + }, + "loadData": false + } + } + ] + }, + { + "key": "child-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "child done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "child-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "child cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-mid.json b/core/Workflows/subflow-override-lab/subflow-override-lab-mid.json new file mode 100644 index 0000000..1a2fc3d --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-mid.json @@ -0,0 +1,129 @@ +{ + "key": "subflow-override-lab-mid", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "mid" + ], + "attributes": { + "type": "S", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab Mid" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "startTransition": { + "key": "start-subflow-override-lab-mid", + "target": "mid-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start Mid" + } + ] + }, + "states": [ + { + "key": "mid-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "mid initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-mid-to-mid-wait", + "target": "mid-wait", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "mid-wait", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "mid-wait" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + } + } + }, + { + "key": "mid-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "mid done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "mid-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "mid cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-parent-legacy.json b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-legacy.json new file mode 100644 index 0000000..8ffa3dd --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-legacy.json @@ -0,0 +1,151 @@ +{ + "key": "subflow-override-lab-parent-legacy", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "parent-legacy" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab parent-legacy" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-parent-legacy", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "views": { + "subflow-override-lab-child-lp-view": { + "key": "subflow-override-lab-legacy-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-parent-nolp.json b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-nolp.json new file mode 100644 index 0000000..fa03fb7 --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-nolp.json @@ -0,0 +1,152 @@ +{ + "key": "subflow-override-lab-parent-nolp", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "parent-nolp" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab parent-nolp" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-parent-nolp", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-plain-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "states": { + "plain-wait": { + "interaction": { + "longPoll": { + "fallbackTimeoutSeconds": 90 + } + } + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-parent-roles.json b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-roles.json new file mode 100644 index 0000000..19703c7 --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-roles.json @@ -0,0 +1,165 @@ +{ + "key": "subflow-override-lab-parent-roles", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "parent-roles" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab parent-roles" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-parent-roles", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "states": { + "lp-wait": { + "interaction": { + "longPoll": { + "roles": [ + { + "role": "ovr.parent-ack", + "grant": "allow" + } + ] + } + }, + "views": { + "subflow-override-lab-child-lp-view": { + "key": "subflow-override-lab-missing-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + } + } + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-parent-short.json b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-short.json new file mode 100644 index 0000000..fe8e169 --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-parent-short.json @@ -0,0 +1,152 @@ +{ + "key": "subflow-override-lab-parent-short", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "parent-short" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab parent-short" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-parent-short", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "states": { + "lp-wait": { + "interaction": { + "longPoll": { + "fallbackTimeoutSeconds": 5 + } + } + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-parent.json b/core/Workflows/subflow-override-lab/subflow-override-lab-parent.json new file mode 100644 index 0000000..474060c --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-parent.json @@ -0,0 +1,172 @@ +{ + "key": "subflow-override-lab-parent", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "parent" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab parent" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-parent", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-child", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "states": { + "lp-wait": { + "interaction": { + "longPoll": { + "fallbackTimeoutSeconds": 120 + } + }, + "views": { + "subflow-override-lab-child-lp-view": { + "key": "subflow-override-lab-parent-lp-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + } + } + } + }, + "transitions": { + "confirm": { + "views": { + "subflow-override-lab-child-confirm-view": { + "key": "subflow-override-lab-parent-confirm-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + } + } + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-plain-child.json b/core/Workflows/subflow-override-lab/subflow-override-lab-plain-child.json new file mode 100644 index 0000000..dc8d58a --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-plain-child.json @@ -0,0 +1,138 @@ +{ + "key": "subflow-override-lab-plain-child", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "plain-child" + ], + "attributes": { + "type": "S", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab Plain Child" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "startTransition": { + "key": "start-subflow-override-lab-plain-child", + "target": "plain-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start Plain Child" + } + ] + }, + "states": [ + { + "key": "plain-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "plain initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-plain-to-plain-wait", + "target": "plain-wait", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "plain-wait", + "stateType": 2, + "subType": 6, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Plain Wait (no long-poll)" + } + ], + "view": { + "view": { + "key": "subflow-override-lab-child-plain-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + }, + "loadData": false + }, + "subFlow": null, + "transitions": [ + { + "key": "finish-plain", + "target": "plain-done", + "triggerType": 0, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Finish" + } + ] + } + ] + }, + { + "key": "plain-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "plain done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "plain-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "plain cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/core/Workflows/subflow-override-lab/subflow-override-lab-top.json b/core/Workflows/subflow-override-lab/subflow-override-lab-top.json new file mode 100644 index 0000000..71474f6 --- /dev/null +++ b/core/Workflows/subflow-override-lab/subflow-override-lab-top.json @@ -0,0 +1,166 @@ +{ + "key": "subflow-override-lab-top", + "flow": "sys-flows", + "flowVersion": "1.0.0", + "domain": "core", + "version": "1.0.2", + "tags": [ + "integration-test", + "subflow-override-lab", + "top" + ], + "attributes": { + "type": "F", + "timeout": null, + "labels": [ + { + "language": "en-US", + "label": "SubFlow Override Lab top" + } + ], + "functions": [], + "features": [], + "extensions": [], + "sharedTransitions": [], + "cancel": { + "key": "cancel-parent", + "target": "parent-cancelled", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Cancel" + } + ] + }, + "startTransition": { + "key": "start-subflow-override-lab-top", + "target": "parent-initial", + "triggerType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "Start" + } + ] + }, + "states": [ + { + "key": "parent-initial", + "stateType": 1, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent initial" + } + ], + "view": null, + "subFlow": null, + "transitions": [ + { + "key": "auto-parent-to-waiting", + "target": "waiting", + "triggerType": 1, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Auto" + } + ], + "rule": { + "location": "./src/OverrideLabAlwaysTrueRule.csx", + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vIEVhY2ggZmxvdyB3YWxrcyBpdHNlbGYgdG8gaXRzIHdhaXRpbmcgc3RhdGUgc28gYSB0ZXN0IG9ubHkgc3RhcnRzIHRoZSB0b3AuCnB1YmxpYyBjbGFzcyBPdmVycmlkZUxhYkFsd2F5c1RydWVSdWxlIDogU2NyaXB0QmFzZSwgSUNvbmRpdGlvbk1hcHBpbmcKewogICAgcHVibGljIFRhc2s8Ym9vbD4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpID0+IFRhc2suRnJvbVJlc3VsdCh0cnVlKTsKfQo=" + } + } + ] + }, + { + "key": "waiting", + "stateType": 4, + "subType": 0, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "waiting" + } + ], + "view": null, + "transitions": [], + "subFlow": { + "type": "S", + "process": { + "key": "subflow-override-lab-mid", + "domain": "core", + "version": "1.0.2", + "flow": "sys-flows" + }, + "mapping": { + "location": "./src/OverrideLabSubFlowMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkR5bmFtaWM7CnVzaW5nIFN5c3RlbS5UaHJlYWRpbmcuVGFza3M7CnVzaW5nIEJCVC5Xb3JrZmxvdy5TY3JpcHRpbmc7CgovLyBQYXNzLXRocm91Z2ggZm9yIGRhdGEuIEl0IERPRVMgZm9yd2FyZCB0aGUgY2FsbGVyJ3Mgcm9sZSBoZWFkZXJzOiBTdWJmbG93U3RhcnRlciBzZW5kcyB0aGUgY2hpbGQKLy8gb25seSB0aGUgaGVhZGVycyB0aGUgaW5wdXQgbWFwcGluZyByZXR1cm5zLCBhbmQgdGhlIGxvbmctcG9sbCBhcm0gKG9yZGVyIDc1KSBwYXVzZXMgb25seSB3aGVuIHRoZQovLyBUUklHR0VSSU5HIGNhbGxlciBzYXRpc2ZpZXMgdGhlIGVmZmVjdGl2ZSBsb25nLXBvbGwgcm9sZXMuIFdpdGhvdXQgdGhpcyBmb3J3YXJkIGEgcm9sZXMtYXJtZWQKLy8gbG9uZy1wb2xsIGluIGEgU3ViRmxvdyBjaGlsZCBuZXZlciBwYXVzZXMsIGFuZCBub3RoaW5nIGFib3V0IHRoZSBvdmVycmlkZSB3b3VsZCBiZSBvYnNlcnZhYmxlLgpwdWJsaWMgY2xhc3MgT3ZlcnJpZGVMYWJTdWJGbG93TWFwcGluZyA6IFNjcmlwdEJhc2UsIElTdWJGbG93TWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICBkeW5hbWljIGlucHV0ID0gbmV3IEV4cGFuZG9PYmplY3QoKTsKICAgICAgICBpbnB1dC5zdGFydGVkQnlPdmVycmlkZUxhYiA9IHRydWU7CgogICAgICAgIHZhciBoZWFkZXJzID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PihTdHJpbmdDb21wYXJlci5PcmRpbmFsSWdub3JlQ2FzZSk7CiAgICAgICAgZm9yZWFjaCAodmFyIG5hbWUgaW4gbmV3W10geyAieC1yb2xlcyIsICJyb2xlIiwgInVzZXJfcmVmZXJlbmNlIiwgIngtZGV2aWNlLWlkIiwgIngtdG9rZW4taWQiIH0pCiAgICAgICAgewogICAgICAgICAgICB0cnkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgc3RyaW5nPyB2YWx1ZSA9IChzdHJpbmc/KWNvbnRleHQuSGVhZGVyc1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkodmFsdWUpKSBoZWFkZXJzW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbikKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gaGVhZGVyIGFic2VudAogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSB7IERhdGEgPSBpbnB1dCwgSGVhZGVycyA9IGhlYWRlcnMgfSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgICAgID0+IFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7Cn0K" + }, + "overrides": { + "states": { + "lp-wait": { + "interaction": { + "longPoll": { + "fallbackTimeoutSeconds": 45, + "roles": [ + { + "role": "ovr.parent-ack", + "grant": "allow" + } + ] + } + }, + "views": { + "subflow-override-lab-child-lp-view": { + "key": "subflow-override-lab-parent-lp-view", + "domain": "core", + "version": "1.0.0", + "flow": "sys-views" + } + } + } + } + } + } + }, + { + "key": "parent-done", + "stateType": 3, + "subType": 1, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent done" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + }, + { + "key": "parent-cancelled", + "stateType": 3, + "subType": 7, + "versionStrategy": "Major", + "labels": [ + { + "language": "en-US", + "label": "parent cancelled" + } + ], + "view": null, + "subFlow": null, + "transitions": [] + } + ] + } +} diff --git a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs new file mode 100644 index 0000000..ca8a43d --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs @@ -0,0 +1,177 @@ +using Core.IntegrationTests.Infrastructure; + +namespace Core.IntegrationTests.Tests.SubflowOverrideLab; + +/// +/// overrides.states.<childState>.interaction.longPoll: the parent tunes the window and the +/// roles of a long-poll its SubFlow child declares. +/// +/// +/// Why this exists. The fallback window is consumed inside the CHILD's pipeline +/// (HandleLongPollTerminationStep, order 75), where the parent's definition is not in scope, so +/// until this change a parent had no way to set it. The override is stamped onto the child at start +/// and resolved by Instance.ResolveEffectiveLongPoll — one resolver behind the arm, the gate +/// and the state body. These tests pin that the three agree, via the parent AND with the child +/// addressed directly. +/// The child declares terminate: true, fallbackTimeoutSeconds: 600, roles: [ovr.child-ack]. +/// +public sealed class LongPollOverrideTests : SubflowOverrideLabTestBase +{ + public LongPollOverrideTests(VNextTestEnvironment environment) : base(environment) { } + + /// + /// Duration-only override: the body reports the PARENT's window, and the child's terminate + /// and roles are kept because the override leaves them out (field-level replace). + /// + [Fact] + public async Task ADurationOverrideReplacesTheWindowAndKeepsTheChildsRolesAndTerminate() + { + var (parentId, childId) = await StartPairAsync(Parent, Child, ChildAck); + await WaitForLeafStateAsync(Parent, parentId, "lp-wait", ChildAck); + + foreach (var (workflow, id) in new[] { (Parent, parentId), (Child, childId) }) + { + var interaction = await InteractionAsync(workflow, id, ChildAck); + Assert.True(interaction.HasValue, + $"{workflow}: the child is parked on its long-poll and ovr.child-ack is the child's own role, " + + "which a duration-only override must keep — the interaction block must be served"); + + Assert.Equal(120, interaction!.Value.GetProperty("fallbackTimeoutSeconds").GetInt32()); + Assert.True(interaction.Value.GetProperty("terminateLongPoll").GetBoolean(), + "terminate is never overridable and the child declares true"); + + Assert.True(await AckAllowedAsync(workflow, id, ChildAck), + $"{workflow}: the child's own role must still be admitted to acknowledge"); + + // The override did not name roles, so it must not have widened them either. + Assert.Null(await InteractionAsync(workflow, id, ParentAck)); + Assert.False(await AckAllowedAsync(workflow, id, ParentAck), + $"{workflow}: ovr.parent-ack is granted by no one here"); + } + } + + /// + /// The window the body reports must be the window the job was ARMED with. Since runtime + /// 5f60ab4b the long-poll ack InstanceJobs row carries ExecuteAt, but no read + /// surface exposes it and this suite has no database access, so it is measured by behaviour — which + /// is also the stronger claim (the job actually fires, not just was scheduled): a 5 s override on a + /// 600 s child must resume the child — token cleared, interaction gone, status Active — within seconds. + /// + [Fact] + public async Task TheArmedFallbackJobFiresOnTheParentsWindow() + { + var (parentId, childId) = await StartPairAsync(ParentShort, Child, ChildAck); + + var interaction = await InteractionAsync(Child, childId, ChildAck); + // Tolerate the rare case the 5 s job already fired before the first read. + if (interaction.HasValue) + Assert.Equal(5, interaction.Value.GetProperty("fallbackTimeoutSeconds").GetInt32()); + + await WaitUntilAsync(async () => + { + var body = await StateBodyAsync(Child, childId, ChildAck); + return body.GetProperty("status").GetString() == "A" + && !body.TryGetProperty("interaction", out _); + }, $"{Child} {childId} should be resumed by the fallback job armed with the parent's 5 s window " + + $"(the child's own window is {ChildWindowSeconds} s)", TimeSpan.FromSeconds(45)); + + var (state, _) = await GetInstanceStateAsync(Child, childId, ChildAck); + Assert.Equal("lp-wait", state); + await AssertNotFaultedAsync(ParentShort, parentId, ChildAck); + } + + /// + /// Roles-only override: the parent's list REPLACES the child's. A role granted only by the parent + /// receives the interaction and may acknowledge; the child's own role is refused — via the parent + /// and with the child addressed directly. The window stays the child's. + /// + [Fact] + public async Task ARolesOverrideReplacesTheChildsGrantsOnEverySurface() + { + var (parentId, childId) = await StartPairAsync(ParentRoles, Child, ParentAck); + await WaitForLeafStateAsync(ParentRoles, parentId, "lp-wait", ParentAck); + + foreach (var (workflow, id) in new[] { (ParentRoles, parentId), (Child, childId) }) + { + var granted = await InteractionAsync(workflow, id, ParentAck); + Assert.True(granted.HasValue, + $"{workflow}: ovr.parent-ack is granted only by the parent's override and must receive the interaction"); + Assert.Equal(ChildWindowSeconds, granted!.Value.GetProperty("fallbackTimeoutSeconds").GetInt32()); + Assert.True(await AckAllowedAsync(workflow, id, ParentAck)); + + Assert.Null(await InteractionAsync(workflow, id, ChildAck)); + Assert.False(await AckAllowedAsync(workflow, id, ChildAck), + $"{workflow}: ovr.child-ack is the child's own grant, which the override REPLACED — not merged"); + } + } + + /// + /// The arm reads the same resolver: a starter holding only the child's own role is not an owner of + /// the stop once the parent replaced the roles, so the child must NOT pause. + /// + [Fact] + public async Task TheArmHonoursTheRolesOverride() + { + var (parentId, childId) = await StartPairAsync(ParentRoles, Child, ChildAck); + await WaitForLeafStateAsync(ParentRoles, parentId, "lp-wait", ChildAck); + + var body = await StateBodyAsync(Child, childId, ParentAck); + Assert.Equal("A", body.GetProperty("status").GetString()); + Assert.False(body.TryGetProperty("interaction", out _), + "the triggering caller held ovr.child-ack only; under the parent's roles override that is not " + + "an owner of the stop, so the arm must not have paused the child"); + } + + /// + /// An override never ADDS a long-poll: the plain child's state declares none, so there is no + /// interaction block and the instance rests Active (log EventId 20305 is checked out-of-band). + /// + [Fact] + public async Task AnOverrideOnAStateWithoutALongPollIsIgnored() + { + var (parentId, childId) = await StartPairAsync(ParentNoLongPoll, PlainChild, ChildAck); + await WaitForLeafStateAsync(ParentNoLongPoll, parentId, "plain-wait", ChildAck); + + foreach (var (workflow, id) in new[] { (ParentNoLongPoll, parentId), (PlainChild, childId) }) + { + var body = await StateBodyAsync(workflow, id, ChildAck); + Assert.False(body.TryGetProperty("interaction", out _), + $"{workflow}: plain-wait declares no long-poll; the parent's override must not create one"); + Assert.True(await AckAllowedAsync(workflow, id, ChildAck), + "nothing is awaiting, and authorize?ack=true answers allowed there"); + } + + var (_, status) = await GetInstanceStateAsync(PlainChild, childId, ChildAck); + Assert.Equal("A", status); + } + + /// + /// One hop: TOP's override names lp-wait, a state that exists only in the GRANDCHILD. It must + /// not reach it — the grandchild keeps its own 600 s window and its own roles. + /// + [Fact] + public async Task AnAncestorsOverrideDoesNotReachTheGrandchild() + { + var (topId, midId) = await StartPairAsync(Top, Mid, ChildAck); + + string? leafId = null; + await WaitUntilAsync(async () => + { + var subs = await GetActiveSubflowsAsync(Mid, midId, ChildAck); + return subs.TryGetValue(Child, out leafId); + }, $"{Mid} {midId} should open a correlation to {Child}"); + await WaitForLeafStateAsync(Top, topId, "lp-wait", ChildAck); + + foreach (var (workflow, id) in new[] { (Top, topId), (Child, leafId!) }) + { + var interaction = await InteractionAsync(workflow, id, ChildAck); + Assert.True(interaction.HasValue, + $"{workflow}: the grandchild's own role must still own and receive the interaction — " + + "had TOP's roles override leaked, ovr.child-ack would not even have armed the pause"); + Assert.Equal(ChildWindowSeconds, interaction!.Value.GetProperty("fallbackTimeoutSeconds").GetInt32()); + + Assert.Null(await InteractionAsync(workflow, id, ParentAck)); + Assert.False(await AckAllowedAsync(workflow, id, ParentAck)); + } + } +} diff --git a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md new file mode 100644 index 0000000..64d6cde --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md @@ -0,0 +1,126 @@ +# subflow-override-lab + +## Ne kontrol ediyor + +Bir parent'ın `state.subFlow.overrides` ile tükettiği SubFlow çocuğuna verdiği iki yeni override'ın +uçtan uca davranışını: + +| Override | Beklenen | +|---|---| +| `overrides.states..interaction.longPoll.fallbackTimeoutSeconds` | Pencere parent'ınki olur; state gövdesi, arm edilen job ve ack aynı değeri görür | +| `overrides.states..interaction.longPoll.roles` | Liste **bütün olarak** değişir (merge yok); yazılmayan alan çocuğunkini korur | +| long-poll'u OLMAYAN bir state'e long-poll override'ı | Yok sayılır, long-poll **eklenmez** (EventId 20305) | +| `overrides.states..views.` | Çocuğun kendi kuralının seçtiği view'un referansı değişir | +| `overrides.transitions..views.` | Yalnız o transition'ın view'u; state override'ı transition view'una düşmez | +| Çözülemeyen view override'ı | Çocuğun kendi view'u döner (EventId 20101) | +| Eski `overrides.views` haritası | Bugünkü gibi: parent-taraflı, yalnız parent poll edildiğinde | + +Override'lar çocuğa başlangıçta damgalanır (`subflow.state_role_overrides` / +`subflow.transition_role_overrides`) ve **çocuk tarafında**, çocuğun kendi `CurrentState`'i üzerinden +çözülür: çocuk doğrudan adreslendiğinde de geçerlidir ve **tek hop**'tur (P'nin override'ı torun G'ye +ulaşmaz). Her iddia hem parent üzerinden hem çocuk doğrudan adreslenerek ölçülür. + +## Neden var + +Runtime değişikliği `feature/subflow-override-interaction-views` (spec +`2026-09-23-subflow-override-interaction-and-views`). Long-poll penceresi çocuğun pipeline'ında +(`HandleLongPollTerminationStep`, order 75) tüketilir ve orada parent'ın tanımı kapsamda değildir; bu +değişiklikten önce parent'ın bunu ayarlamasının yolu yoktu. Düz `viewKey → Reference` haritası da bir +view'u her yerde birden değiştiriyordu. + +## Neden ayrı bir akış + +`authorization-chain-lab`'in leaf'i zaten bir long-poll taşıyor ve `AckAndParentRetainedTests` onun +`roles = [chain.admin]` olduğunu varsayıyor; pencereyi ya da rolleri override etmek o suite'in ack +sözleşmesini değiştirirdi. + +## Akışlar + +Üretici: `core/Workflows/subflow-override-lab/build-subflow-override-lab.py` (view'lar +`core/Views/subflow-override-lab/`). + +``` +CHILD (S) child-initial --auto--> lp-wait + lp-wait: longPoll { terminate, fallback 600, roles [ovr.child-ack] }, view child-lp-view + transitions: confirm (view child-confirm-view), note (view child-lp-view) +PLAIN-CHILD (S) plain-initial --auto--> plain-wait (long-poll YOK) + +PARENT -> CHILD lp-wait: fallback 120 (yalnız süre) + views{child-lp-view->parent-lp-view} + transitions.confirm.views{child-confirm-view->parent-confirm-view} +PARENT-SHORT -> CHILD lp-wait: fallback 5 (pencerenin davranışsal kanıtı) +PARENT-ROLES -> CHILD lp-wait: roles [ovr.parent-ack] (yalnız roller) + views{child-lp-view->YOK olan view} +PARENT-NOLP -> PLAIN plain-wait: fallback 90 (yok sayılmalı) +PARENT-LEGACY -> CHILD overrides.views{child-lp-view->legacy-view} (eski harita) +TOP -> MID (S, override'sız) -> CHILD + TOP'un override'ı `lp-wait`'i adlandırır — MID'de yok, torunda var +``` + +**Rol başlıkları çocuğa iletilir.** `SubflowStarter` çocuğa yalnız input mapping'in döndürdüğü +header'ları gönderir, ve long-poll arm'ı (order 75) yalnız **tetikleyen çağıran** etkin long-poll +rollerini karşılıyorsa durur. Lab'ın SubFlow mapping'i bu yüzden `x-roles`/`role`'ü iletir; onsuz +roles-kollu bir long-poll SubFlow çocuğunda hiç durmaz (authorization-chain-lab leaf'inin 575 Active / +0 token satırı bunun ölçümü). + +## Testler + +`LongPollOverrideTests` (6) + `ViewOverrideTests` (6): + +- `ADurationOverrideReplacesTheWindowAndKeepsTheChildsRolesAndTerminate` +- `TheArmedFallbackJobFiresOnTheParentsWindow` — 5 s override, 600 s çocuk; çocuk saniyeler içinde devam etmeli +- `ARolesOverrideReplacesTheChildsGrantsOnEverySurface` — state function + `authorize?ack=true`, parent ve çocuk +- `TheArmHonoursTheRolesOverride` — yalnız çocuğun kendi rolüyle başlatılan çocuk durmamalı +- `AnOverrideOnAStateWithoutALongPollIsIgnored` +- `AnAncestorsOverrideDoesNotReachTheGrandchild` (long-poll) / `AnAncestorsViewOverrideDoesNotReachTheGrandchild` +- `AStateScopedOverrideAppliesViaTheParentAndOnTheDirectlyAddressedChild` +- `ATransitionScopedOverrideAppliesToThatTransitionsView` +- `AStateOverrideDoesNotLeakIntoATransitionView` +- `AnUnresolvableOverrideFallsBackToTheChildsView` +- `TheLegacyViewMapStillAppliesParentSideOnly` + +## Nasıl koşulur + +Ön koşul: infra + runtime lokal build ayakta (`etc/docker/run-docker.sh up core`), `VNEXT_BASE_URL` +`test.runsettings` içinde. MockLab **gerekmez**. + +```bash +dotnet test tests/Core.IntegrationTests --settings tests/Core.IntegrationTests/test.runsettings \ + --filter "FullyQualifiedName~SubflowOverrideLab" -v minimal +``` + +Loglar (20300/20305/20101) ve persist edilen satırlar test dışında kontrol edilir — bkz. koşu kaydı. + +## Bilinen açıklar + +- **Şema:** kurulu `@burgan-tech/vnext-schema` (0.0.52) `subFlowStateOverride.interaction`/`views` ve + `subFlowTransitionOverride.views` alanlarını tanımıyor; beş parent akışı `npm run validate`'ten + `must NOT have additional property "interaction"` / `"views"` ile düşer. SDK JSON'u doğrudan + `/definitions/publish`'e gönderdiği için test engeli değil. Şema değişikliği yerel bir vnext-schema + dalında, yayınlanmadı. +- **Pencere API'den okunamıyor:** runtime `5f60ab4b` ile long-poll ack `InstanceJobs` satırı + (`JobType 4`, `…longpoll-ack`) artık `ExecuteAt` taşıyor — Dapr job'unun kurulduğu anın aynısı, etkin + pencereden (child'ın kendi değeri ya da parent override'ı). Ancak hiçbir okuma yüzeyi bu satırı + göstermiyor ve suite'in veritabanı erişimi yok; test bu yüzden davranışsal ölçümü korur (5 s testi: + job'un gerçekten ateşlendiğini kanıtlar, yalnız planlandığını değil). Önceki runtime'da (`e7cb023c`) + `ExecuteAt` NULL'dı ve pencere işlenen satırın `ModifiedAt − CreatedAt` farkından ölçülmüştü + (120.2 s / 5.1 s). Postgres doğrulaması (2026-09-23, `subflow_override_lab_child.InstanceJobs`, + `JobType=4`): düzeltme öncesi 22 satırın 0'ında `ExecuteAt` dolu, düzeltme sonrası 10/10 dolu; + `ExecuteAt − CreatedAt` = 600.0 / 120.0 / 5.0 s (child'ın kendi penceresi / parent override / kısa + override). + +## Koşu kaydı + +**2026-09-23 — 12/12** (iki koşu), runtime lokal build `feature/subflow-override-interaction-views` +@ `e7cb023c`, `VNEXT_BASE_URL=http://localhost:4201`. Komşu suite'ler aynı runtime'da: +AuthorizationChainLab 44/44 (+7 morph-idm skip), SubflowOrchestration 22/22, TimeoutLab 3/3. + +Test dışı kanıt (host log + postgres; OpenObserve MCP bu oturumda bağlı değildi): + +- 20300 `Long-poll termination armed … fallback in 120s` ×4 (PARENT), `in 5s` ×1 (PARENT-SHORT), + `in 600s` ×5 (PARENT-ROLES ack rolüyle ×2, LEGACY, TOP torunları ×2). +- İşlenen job satırları: PARENT çocuğu `CreatedAt 19:45:36.709 → ModifiedAt 19:47:36.874` (120.2 s); + PARENT-SHORT çocuğu `19:49:59.512 → 19:50:04.591` (5.1 s). Çocuğun kendi değeri 600 s. +- PARENT-ROLES çocuğu yalnız `ovr.child-ack` ile başlatıldığında: job satırı yok, `Status A`, token yok. +- 20305 `Long-poll override ignored … at state plain-wait` ×1; PLAIN-CHILD'da `InstanceJobs` 0 satır. +- 20101 `SubFlow view override unresolved … child-lp-view -> subflow-override-lab-missing-view` ×2. +- MID'in `subflow.state_role_overrides` damgası TOP'un `lp-wait` override'ını taşıyor; torunun + ExtraProperties'inde damga **yok**. diff --git a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/SubflowOverrideLabTestBase.cs b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/SubflowOverrideLabTestBase.cs new file mode 100644 index 0000000..4021a7e --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/SubflowOverrideLabTestBase.cs @@ -0,0 +1,137 @@ +using System.Net; +using System.Text.Json; +using Core.IntegrationTests.Infrastructure; + +namespace Core.IntegrationTests.Tests.SubflowOverrideLab; + +/// +/// Shared plumbing for the subflow-override-lab suite: a parent's +/// overrides.states.<childState>.interaction.longPoll and scoped views overrides, +/// resolved CHILD-side on the child's own state. +/// +/// Every flow walks itself to its waiting state, so a test starts the top instance and waits for the +/// correlation. Reads go out over because several +/// assertions are about a status code (authorize answers 403 with the verdict in the body). +/// +/// +public abstract class SubflowOverrideLabTestBase : WorkflowTestBase +{ + protected const string Child = "subflow-override-lab-child"; + protected const string PlainChild = "subflow-override-lab-plain-child"; + protected const string Mid = "subflow-override-lab-mid"; + + /// Duration-only override (120 s) + state- and transition-scoped view overrides. + protected const string Parent = "subflow-override-lab-parent"; + /// Duration-only override of 5 s — the behavioural probe for the armed window. + protected const string ParentShort = "subflow-override-lab-parent-short"; + /// Roles-only override ([ovr.parent-ack]) + a view override pointing at a view that does not exist. + protected const string ParentRoles = "subflow-override-lab-parent-roles"; + /// A long-poll override on a child state that declares no long-poll. + protected const string ParentNoLongPoll = "subflow-override-lab-parent-nolp"; + /// The deprecated parent-side overrides.views map. + protected const string ParentLegacy = "subflow-override-lab-parent-legacy"; + /// TOP → MID → CHILD; TOP's override names a state that exists only in the grandchild. + protected const string Top = "subflow-override-lab-top"; + + /// The child's OWN long-poll role. + protected const string ChildAck = "ovr.child-ack"; + /// Granted only by a parent's roles override. + protected const string ParentAck = "ovr.parent-ack"; + + /// The child's declared window; every override below is far from it. + protected const int ChildWindowSeconds = 600; + + protected const string ChildLpView = "subflow-override-lab-child-lp-view"; + protected const string ChildConfirmView = "subflow-override-lab-child-confirm-view"; + protected const string ChildPlainView = "subflow-override-lab-child-plain-view"; + protected const string ParentLpView = "subflow-override-lab-parent-lp-view"; + protected const string ParentConfirmView = "subflow-override-lab-parent-confirm-view"; + protected const string LegacyView = "subflow-override-lab-legacy-view"; + + protected SubflowOverrideLabTestBase(VNextTestEnvironment environment) : base(environment) { } + + /// + /// Starts as and returns the id of the + /// instance correlated as beneath it. + /// + /// The starter's roles matter: the long-poll arm (order 75) pauses only when the TRIGGERING + /// caller satisfies the effective long-poll roles, and the lab's SubFlow mapping forwards the + /// caller's role headers to the child for exactly that reason. + /// + /// + protected async Task<(string ParentId, string ChildId)> StartPairAsync( + string workflow, string childWorkflow, string roles) + { + var parentId = await StartAsync(workflow, new { labRef = $"ovr-{Guid.NewGuid():N}"[..20] }, roles); + await AssertNotFaultedAsync(workflow, parentId, roles); + + string? childId = null; + await WaitUntilAsync(async () => + { + var subs = await GetActiveSubflowsAsync(workflow, parentId, roles); + return subs.TryGetValue(childWorkflow, out childId); + }, $"{workflow} {parentId} should open a correlation to {childWorkflow}"); + + return (parentId, childId!); + } + + /// Waits until the leaf the parent observes has reached . + protected Task WaitForLeafStateAsync(string workflow, string instanceId, string state, string roles) => + WaitUntilAsync(async () => + { + var body = await StateBodyAsync(workflow, instanceId, roles); + return body.GetProperty("state").GetString() == state; + }, $"{workflow} {instanceId} should observe {state}"); + + /// The state function body as sees it (asserting 200). + protected async Task StateBodyAsync(string workflow, string instanceId, string? roles) + { + var (status, body) = await SendRawAsync(HttpMethod.Get, + $"api/v1/core/workflows/{workflow}/instances/{instanceId}/functions/state", headers: Headers(roles)); + Assert.Equal(HttpStatusCode.OK, status); + return JsonDocument.Parse(body).RootElement.Clone(); + } + + /// The interaction block, or null when the body carries none. + protected async Task InteractionAsync(string workflow, string instanceId, string? roles) + { + var body = await StateBodyAsync(workflow, instanceId, roles); + return body.TryGetProperty("interaction", out var interaction) && interaction.ValueKind == JsonValueKind.Object + ? interaction + : null; + } + + /// + /// authorize?ack=true: true on 200, false on 403, and the body's allowed must agree. + /// Anything else fails — a 404 or 500 must not be read as a refusal. + /// + protected async Task AckAllowedAsync(string workflow, string instanceId, string? roles) + { + var (status, raw) = await SendRawAsync(HttpMethod.Get, + $"api/v1/core/workflows/{workflow}/instances/{instanceId}/functions/authorize?ack=true", + headers: Headers(roles)); + + Assert.True(status is HttpStatusCode.OK or HttpStatusCode.Forbidden, + $"authorize?ack=true answered {(int)status}: {raw}"); + + if (!string.IsNullOrWhiteSpace(raw)) + { + var body = JsonDocument.Parse(raw).RootElement; + if (body.ValueKind == JsonValueKind.Object && body.TryGetProperty("allowed", out var allowed)) + Assert.Equal(status == HttpStatusCode.OK, allowed.GetBoolean()); + } + + return status == HttpStatusCode.OK; + } + + /// The key of the view the view function served. + protected async Task ViewKeyAsync( + string workflow, string instanceId, string roles, string? transitionKey = null) + { + var url = $"api/v1/core/workflows/{workflow}/instances/{instanceId}/functions/view" + + (transitionKey is null ? "" : $"?transitionKey={transitionKey}"); + var (status, body) = await SendRawAsync(HttpMethod.Get, url, headers: Headers(roles)); + Assert.True(status == HttpStatusCode.OK, $"view function answered {(int)status}: {body}"); + return JsonDocument.Parse(body).RootElement.GetProperty("key").GetString(); + } +} diff --git a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/ViewOverrideTests.cs b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/ViewOverrideTests.cs new file mode 100644 index 0000000..199da23 --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/ViewOverrideTests.cs @@ -0,0 +1,98 @@ +using Core.IntegrationTests.Infrastructure; + +namespace Core.IntegrationTests.Tests.SubflowOverrideLab; + +/// +/// overrides.states.<childState>.views and overrides.transitions.<childTransition>.views: +/// the parent swaps the reference of the view the CHILD's own rules selected, scoped to one state or +/// one transition. +/// +/// +/// The legacy flat overrides.views map swapped a view key everywhere at once and was applied +/// parent-side after descent. The scoped form is resolved child-side on the child's own +/// CurrentState, so it also applies to a directly addressed child, and a state override never +/// falls through to a transition view (or the reverse). +/// +public sealed class ViewOverrideTests : SubflowOverrideLabTestBase +{ + public ViewOverrideTests(VNextTestEnvironment environment) : base(environment) { } + + [Fact] + public async Task AStateScopedOverrideAppliesViaTheParentAndOnTheDirectlyAddressedChild() + { + var (parentId, childId) = await StartPairAsync(Parent, Child, ChildAck); + await WaitForLeafStateAsync(Parent, parentId, "lp-wait", ChildAck); + + Assert.Equal(ParentLpView, await ViewKeyAsync(Parent, parentId, ChildAck)); + Assert.Equal(ParentLpView, await ViewKeyAsync(Child, childId, ChildAck)); + } + + [Fact] + public async Task ATransitionScopedOverrideAppliesToThatTransitionsView() + { + var (parentId, childId) = await StartPairAsync(Parent, Child, ChildAck); + await WaitForLeafStateAsync(Parent, parentId, "lp-wait", ChildAck); + + Assert.Equal(ParentConfirmView, await ViewKeyAsync(Parent, parentId, ChildAck, "confirm")); + Assert.Equal(ParentConfirmView, await ViewKeyAsync(Child, childId, ChildAck, "confirm")); + } + + /// + /// note's transition view has the SAME key the state override swaps, and no transition + /// override. A transition view is looked up under transitions only, so it must stay the child's. + /// + [Fact] + public async Task AStateOverrideDoesNotLeakIntoATransitionView() + { + var (parentId, childId) = await StartPairAsync(Parent, Child, ChildAck); + await WaitForLeafStateAsync(Parent, parentId, "lp-wait", ChildAck); + + Assert.Equal(ChildLpView, await ViewKeyAsync(Parent, parentId, ChildAck, "note")); + Assert.Equal(ChildLpView, await ViewKeyAsync(Child, childId, ChildAck, "note")); + } + + /// + /// A replacement that cannot be resolved falls back to the child's own view (EventId 20101 is + /// checked out-of-band) — never an error to the client. + /// + [Fact] + public async Task AnUnresolvableOverrideFallsBackToTheChildsView() + { + var (parentId, childId) = await StartPairAsync(ParentRoles, Child, ParentAck); + await WaitForLeafStateAsync(ParentRoles, parentId, "lp-wait", ParentAck); + + Assert.Equal(ChildLpView, await ViewKeyAsync(ParentRoles, parentId, ParentAck)); + Assert.Equal(ChildLpView, await ViewKeyAsync(Child, childId, ParentAck)); + } + + /// One hop: TOP's view override names a grandchild state and must not reach it. + [Fact] + public async Task AnAncestorsViewOverrideDoesNotReachTheGrandchild() + { + var (topId, midId) = await StartPairAsync(Top, Mid, ChildAck); + string? leafId = null; + await WaitUntilAsync(async () => + { + var subs = await GetActiveSubflowsAsync(Mid, midId, ChildAck); + return subs.TryGetValue(Child, out leafId); + }, $"{Mid} {midId} should open a correlation to {Child}"); + await WaitForLeafStateAsync(Top, topId, "lp-wait", ChildAck); + + Assert.Equal(ChildLpView, await ViewKeyAsync(Top, topId, ChildAck)); + Assert.Equal(ChildLpView, await ViewKeyAsync(Child, leafId!, ChildAck)); + } + + /// + /// Regression: the deprecated parent-side map keeps today's behaviour — applied when the PARENT is + /// polled (after descent), not when the child is addressed directly. + /// + [Fact] + public async Task TheLegacyViewMapStillAppliesParentSideOnly() + { + var (parentId, childId) = await StartPairAsync(ParentLegacy, Child, ChildAck); + await WaitForLeafStateAsync(ParentLegacy, parentId, "lp-wait", ChildAck); + + Assert.Equal(LegacyView, await ViewKeyAsync(ParentLegacy, parentId, ChildAck)); + Assert.Equal(ChildLpView, await ViewKeyAsync(Child, childId, ChildAck)); + } +}