From 04d54b5931956c5eb0c6ecce69cbd13d65a732f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Wed, 23 Sep 2026 23:02:54 +0300 Subject: [PATCH 1/2] test(subflow-override-lab): cover parent long-poll and scoped view overrides end to end New scenario for runtime feature/subflow-override-interaction-views: a parent's overrides.states..interaction.longPoll (fallbackTimeoutSeconds, roles) and overrides.states/transitions..views, resolved child-side on the child's own state. - 9 flows + 6 views, generated by build-subflow-override-lab.py - LongPollOverrideTests (6): window in state body, armed job fires on the parent's window (5 s override on a 600 s child), roles replace on state function + authorize?ack=true via parent and child-direct, arm honours the roles override, no long-poll added to a state without one, one hop (P's override does not reach G) - ViewOverrideTests (6): state- and transition-scoped swap via parent and child-direct, no state->transition leak, unresolvable override falls back, one hop, legacy overrides.views still parent-side only - README + TEST-SCENARIOS row 12/12 against the locally built runtime (e7cb023c); neighbours AuthorizationChainLab 44/44, SubflowOrchestration 22/22, TimeoutLab 3/3. Co-Authored-By: Claude Opus 5.5 --- TEST-SCENARIOS.md | 1 + ...bflow-override-lab-child-confirm-view.json | 28 ++ .../subflow-override-lab-child-lp-view.json | 28 ++ ...subflow-override-lab-child-plain-view.json | 28 ++ .../subflow-override-lab-legacy-view.json | 28 ++ ...flow-override-lab-parent-confirm-view.json | 28 ++ .../subflow-override-lab-parent-lp-view.json | 28 ++ .../build-subflow-override-lab.py | 413 ++++++++++++++++++ .../src/OverrideLabAlwaysTrueRule.csx | 8 + .../src/OverrideLabSubFlowMapping.csx | 37 ++ .../subflow-override-lab-child.json | 180 ++++++++ .../subflow-override-lab-mid.json | 129 ++++++ .../subflow-override-lab-parent-legacy.json | 151 +++++++ .../subflow-override-lab-parent-nolp.json | 152 +++++++ .../subflow-override-lab-parent-roles.json | 165 +++++++ .../subflow-override-lab-parent-short.json | 152 +++++++ .../subflow-override-lab-parent.json | 172 ++++++++ .../subflow-override-lab-plain-child.json | 138 ++++++ .../subflow-override-lab-top.json | 166 +++++++ .../LongPollOverrideTests.cs | 176 ++++++++ .../Tests/SubflowOverrideLab/README.md | 120 +++++ .../SubflowOverrideLabTestBase.cs | 137 ++++++ .../SubflowOverrideLab/ViewOverrideTests.cs | 98 +++++ 23 files changed, 2563 insertions(+) create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-child-confirm-view.json create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-child-lp-view.json create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-child-plain-view.json create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-legacy-view.json create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-parent-confirm-view.json create mode 100644 core/Views/subflow-override-lab/subflow-override-lab-parent-lp-view.json create mode 100644 core/Workflows/subflow-override-lab/build-subflow-override-lab.py create mode 100644 core/Workflows/subflow-override-lab/src/OverrideLabAlwaysTrueRule.csx create mode 100644 core/Workflows/subflow-override-lab/src/OverrideLabSubFlowMapping.csx create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-child.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-mid.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-parent-legacy.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-parent-nolp.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-parent-roles.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-parent-short.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-parent.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-plain-child.json create mode 100644 core/Workflows/subflow-override-lab/subflow-override-lab-top.json create mode 100644 tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs create mode 100644 tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md create mode 100644 tests/Core.IntegrationTests/Tests/SubflowOverrideLab/SubflowOverrideLabTestBase.cs create mode 100644 tests/Core.IntegrationTests/Tests/SubflowOverrideLab/ViewOverrideTests.cs diff --git a/TEST-SCENARIOS.md b/TEST-SCENARIOS.md index 52108e1..09867f8 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 kalıcı olarak okunamadığı için (long-poll ack `InstanceJobs.ExecuteAt` NULL, Dapr job `directly`) davranışsal ölçüldü: işlenen job `ModifiedAt−CreatedAt` = 120.2 s (override 120, çocuk 600) ve 5.1 s (override 5). **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..f2b026f --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs @@ -0,0 +1,176 @@ +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. It is not persisted + /// anywhere readable (the long-poll ack InstanceJobs row has no ExecuteAt, and the + /// Dapr job is scheduled directly), so it is measured by behaviour: 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..48911d8 --- /dev/null +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md @@ -0,0 +1,120 @@ +# 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 kalıcı olarak okunamıyor:** long-poll ack `InstanceJobs` satırı (`JobType 4`, + `…longpoll-ack`) `ExecuteAt` taşımıyor (`HandleLongPollTerminationStep.ScheduleFallbackAsync` + `InstanceJob.Create`'e `executeAt` geçmiyor) ve Dapr job'u `directly: true` ile planlanıyor. Pencere bu + yüzden davranışsal ölçülür: işlenen job satırının `ModifiedAt − CreatedAt` farkı ve 5 s testi. + +## 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)); + } +} From 1111eacbf894fc15e4d6d18e0b5393d8f127191d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Wed, 23 Sep 2026 23:22:41 +0300 Subject: [PATCH 2/2] docs(subflow-override-lab): record that the long-poll ack job now persists ExecuteAt Runtime 5f60ab4b writes the fallback deadline to the InstanceJobs row. The test stays behavioural (no read surface exposes the row and the suite has no database access); the texts that said ExecuteAt is NULL now record the before/after postgres measurement. Co-Authored-By: Claude Opus 5.5 --- TEST-SCENARIOS.md | 2 +- .../SubflowOverrideLab/LongPollOverrideTests.cs | 9 +++++---- .../Tests/SubflowOverrideLab/README.md | 14 ++++++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/TEST-SCENARIOS.md b/TEST-SCENARIOS.md index 09867f8..60ad24f 100644 --- a/TEST-SCENARIOS.md +++ b/TEST-SCENARIOS.md @@ -33,7 +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 kalıcı olarak okunamadığı için (long-poll ack `InstanceJobs.ExecuteAt` NULL, Dapr job `directly`) davranışsal ölçüldü: işlenen job `ModifiedAt−CreatedAt` = 120.2 s (override 120, çocuk 600) ve 5.1 s (override 5). **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 | +| **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/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs index f2b026f..ca8a43d 100644 --- a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/LongPollOverrideTests.cs @@ -51,10 +51,11 @@ public async Task ADurationOverrideReplacesTheWindowAndKeepsTheChildsRolesAndTer } /// - /// The window the body reports must be the window the job was ARMED with. It is not persisted - /// anywhere readable (the long-poll ack InstanceJobs row has no ExecuteAt, and the - /// Dapr job is scheduled directly), so it is measured by behaviour: a 5 s override on a 600 s child - /// must resume the child — token cleared, interaction gone, status Active — within seconds. + /// 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() diff --git a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md index 48911d8..64d6cde 100644 --- a/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md +++ b/tests/Core.IntegrationTests/Tests/SubflowOverrideLab/README.md @@ -96,10 +96,16 @@ Loglar (20300/20305/20101) ve persist edilen satırlar test dışında kontrol e `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 kalıcı olarak okunamıyor:** long-poll ack `InstanceJobs` satırı (`JobType 4`, - `…longpoll-ack`) `ExecuteAt` taşımıyor (`HandleLongPollTerminationStep.ScheduleFallbackAsync` - `InstanceJob.Create`'e `executeAt` geçmiyor) ve Dapr job'u `directly: true` ile planlanıyor. Pencere bu - yüzden davranışsal ölçülür: işlenen job satırının `ModifiedAt − CreatedAt` farkı ve 5 s testi. +- **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ı