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