From af4ca8fdf1d6f2e348ee6e1da50719e083d20809 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 17 Jun 2026 16:31:10 +0000 Subject: [PATCH] refactor: dedup asset-graph code, squash migrations, drop artifacts Co-Authored-By: Claude Opus 4.8 (1M context) --- ...9c3d7b2fb156a43299e9a1209d05293c4f50f.json | 64 +++ ...7bb759229805cff771cfa4275a46bfc80e1ab.json | 15 + ...8364a3489a3c9151ac153c419ddce51d0d891.json | 50 --- backend/colors-after.png | Bin 73877 -> 0 bytes backend/colors-v2-edit.png | Bin 78450 -> 0 bytes backend/colors-v2.png | Bin 74815 -> 0 bytes .../20260423050000_script_trigger.up.sql | 17 +- ...90545_add_script_trigger_join_all.down.sql | 2 - ...6190545_add_script_trigger_join_all.up.sql | 9 - ...6201317_script_trigger_debounce_s.down.sql | 2 - ...516201317_script_trigger_debounce_s.up.sql | 8 - ...260523054912_script_trigger_retry.down.sql | 3 - ...20260523054912_script_trigger_retry.up.sql | 10 - .../20260523055641_dispatch_event.up.sql | 32 +- ...backfill_asset_from_script_assets.down.sql | 6 - ...7_backfill_asset_from_script_assets.up.sql | 39 -- .../windmill-parser/src/asset_parser.rs | 94 +++-- backend/windmill-api/src/db.rs | 6 + backend/windmill-common/src/assets.rs | 11 +- backend/windmill-common/src/partition.rs | 64 ++- backend/windmill-queue/src/asset_dispatch.rs | 277 +++++++----- backend/windmill-types/src/assets.rs | 17 + .../windmill-worker/src/result_processor.rs | 16 +- backend/windmill-worker/src/worker.rs | 9 +- cli/src/commands/pipeline/pipeline.ts | 49 ++- data-pipelines-sync-demo.zip | Bin 14832 -> 0 bytes .../AssetGraph/AssetGraphDetailsPane.svelte | 1 - .../assets/AssetGraph/AssetRunsPanel.svelte | 1 + .../AssetGraph/PipelineTriggerEditors.svelte | 202 +++++++++ .../assets/AssetGraph/deployGraphDiff.ts | 9 +- .../assets/AssetGraph/graphTraversal.test.ts | 15 +- .../assets/AssetGraph/graphTraversal.ts | 21 +- .../lib/components/assets/AssetGraph/lib.ts | 35 ++ .../parsePipelineAnnotations.parity.test.ts | 42 +- .../AssetGraph/pipelineHistory.svelte.ts | 12 +- .../assets/AssetGraph/pipelineTemplates.ts | 25 +- .../assets/AssetGraph/resolveGraph.ts | 395 ++++++++++-------- .../runs/DispatchEventsButton.svelte | 12 +- .../runs/DispatchEventsPanel.svelte | 12 +- .../runs/DispatchEventsTable.svelte | 3 + .../runs/useDispatchEvents.svelte.ts | 24 ++ .../triggers/TriggerRunnablePicker.svelte | 59 +++ .../email/EmailTriggerEditorInner.svelte | 61 ++- .../triggers/gcp/GcpTriggerEditorInner.svelte | 46 +- .../kafka/KafkaTriggerEditorInner.svelte | 39 +- .../mqtt/MqttTriggerEditorInner.svelte | 45 +- .../nats/NatsTriggerEditorInner.svelte | 39 +- .../PostgresTriggerEditorInner.svelte | 37 +- .../triggers/sqs/SqsTriggerEditorInner.svelte | 46 +- .../(logged)/pipeline/[folder]/+page.svelte | 336 ++++----------- 50 files changed, 1312 insertions(+), 1005 deletions(-) create mode 100644 backend/.sqlx/query-6aaddd80f8c07cfafea2021c1879c3d7b2fb156a43299e9a1209d05293c4f50f.json create mode 100644 backend/.sqlx/query-d3d87b9a4d62977dea5af95dd457bb759229805cff771cfa4275a46bfc80e1ab.json delete mode 100644 backend/.sqlx/query-f7790aaa332e81046ececc71b728364a3489a3c9151ac153c419ddce51d0d891.json delete mode 100644 backend/colors-after.png delete mode 100644 backend/colors-v2-edit.png delete mode 100644 backend/colors-v2.png delete mode 100644 backend/migrations/20260516190545_add_script_trigger_join_all.down.sql delete mode 100644 backend/migrations/20260516190545_add_script_trigger_join_all.up.sql delete mode 100644 backend/migrations/20260516201317_script_trigger_debounce_s.down.sql delete mode 100644 backend/migrations/20260516201317_script_trigger_debounce_s.up.sql delete mode 100644 backend/migrations/20260523054912_script_trigger_retry.down.sql delete mode 100644 backend/migrations/20260523054912_script_trigger_retry.up.sql delete mode 100644 backend/migrations/20260523091727_backfill_asset_from_script_assets.down.sql delete mode 100644 backend/migrations/20260523091727_backfill_asset_from_script_assets.up.sql delete mode 100644 data-pipelines-sync-demo.zip create mode 100644 frontend/src/lib/components/assets/AssetGraph/PipelineTriggerEditors.svelte create mode 100644 frontend/src/lib/components/assets/AssetGraph/lib.ts create mode 100644 frontend/src/lib/components/runs/useDispatchEvents.svelte.ts create mode 100644 frontend/src/lib/components/triggers/TriggerRunnablePicker.svelte diff --git a/backend/.sqlx/query-6aaddd80f8c07cfafea2021c1879c3d7b2fb156a43299e9a1209d05293c4f50f.json b/backend/.sqlx/query-6aaddd80f8c07cfafea2021c1879c3d7b2fb156a43299e9a1209d05293c4f50f.json new file mode 100644 index 0000000000..144bcb13f2 --- /dev/null +++ b/backend/.sqlx/query-6aaddd80f8c07cfafea2021c1879c3d7b2fb156a43299e9a1209d05293c4f50f.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO dispatch_event (\n workspace_id, producer_job_id, subscriber_path,\n asset_kind, asset_path, outcome,\n child_job_id, partition,\n received_inputs, required_inputs,\n debounce_s, reason\n )\n SELECT $1, $2, sp, ak, ap, oc, cj, pt, ri, rq, db, rs\n FROM unnest(\n $3::text[], $4::ASSET_KIND[], $5::text[], $6::DISPATCH_OUTCOME[],\n $7::uuid[], $8::text[], $9::int[], $10::int[], $11::int[], $12::text[]\n ) AS t(sp, ak, ap, oc, cj, pt, ri, rq, db, rs)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Uuid", + "TextArray", + { + "Custom": { + "name": "asset_kind[]", + "kind": { + "Array": { + "Custom": { + "name": "asset_kind", + "kind": { + "Enum": [ + "s3object", + "resource", + "variable", + "ducklake", + "datatable", + "volume" + ] + } + } + } + } + } + }, + "TextArray", + { + "Custom": { + "name": "dispatch_outcome[]", + "kind": { + "Array": { + "Custom": { + "name": "dispatch_outcome", + "kind": { + "Enum": [ + "dispatched", + "join_pending", + "skipped" + ] + } + } + } + } + } + }, + "UuidArray", + "TextArray", + "Int4Array", + "Int4Array", + "Int4Array", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "6aaddd80f8c07cfafea2021c1879c3d7b2fb156a43299e9a1209d05293c4f50f" +} diff --git a/backend/.sqlx/query-d3d87b9a4d62977dea5af95dd457bb759229805cff771cfa4275a46bfc80e1ab.json b/backend/.sqlx/query-d3d87b9a4d62977dea5af95dd457bb759229805cff771cfa4275a46bfc80e1ab.json new file mode 100644 index 0000000000..b5d905b9b6 --- /dev/null +++ b/backend/.sqlx/query-d3d87b9a4d62977dea5af95dd457bb759229805cff771cfa4275a46bfc80e1ab.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job\n SET args = CASE\n WHEN args ? 'partition'\n THEN $1 || jsonb_build_object('partition', args -> 'partition')\n ELSE $1\n END,\n preprocessed = TRUE\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "d3d87b9a4d62977dea5af95dd457bb759229805cff771cfa4275a46bfc80e1ab" +} diff --git a/backend/.sqlx/query-f7790aaa332e81046ececc71b728364a3489a3c9151ac153c419ddce51d0d891.json b/backend/.sqlx/query-f7790aaa332e81046ececc71b728364a3489a3c9151ac153c419ddce51d0d891.json deleted file mode 100644 index c28d5f488a..0000000000 --- a/backend/.sqlx/query-f7790aaa332e81046ececc71b728364a3489a3c9151ac153c419ddce51d0d891.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO dispatch_event (\n workspace_id, producer_job_id, subscriber_path,\n asset_kind, asset_path, outcome,\n child_job_id, partition,\n received_inputs, required_inputs,\n debounce_s, reason\n ) VALUES ($1, $2, $3, $4::ASSET_KIND, $5, $6::DISPATCH_OUTCOME,\n $7, $8, $9, $10, $11, $12)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Uuid", - "Varchar", - { - "Custom": { - "name": "asset_kind", - "kind": { - "Enum": [ - "s3object", - "resource", - "variable", - "ducklake", - "datatable", - "volume" - ] - } - } - }, - "Text", - { - "Custom": { - "name": "dispatch_outcome", - "kind": { - "Enum": [ - "dispatched", - "join_pending", - "skipped" - ] - } - } - }, - "Uuid", - "Text", - "Int4", - "Int4", - "Int4", - "Text" - ] - }, - "nullable": [] - }, - "hash": "f7790aaa332e81046ececc71b728364a3489a3c9151ac153c419ddce51d0d891" -} diff --git a/backend/colors-after.png b/backend/colors-after.png deleted file mode 100644 index 0264d011ea669a2f8d8a08082e43d1f09a412475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73877 zcmb5VXH=70*EWi>g)LIH1(3eC2&f3C^o|Xr2-2lSX`v&%hM)+j2#7T4AksUbLlTOJ z)X+lp;<`I9_qp?Q{V#2vZ8ljaG~C9se=P=$2yL0 z9UYOA;DcWZE}o9Ps6tm?sm5ZdnD&mJRPclr@}J`8g{gQLEMjh;{-%VR5+QWyL>OjU z<1%JAedn66w=_HBcNow0wFmz-qBxn(UH`AC^2Z;q{%dXj^;Z;QB#fuvu@)1{zg86I zx&Ko`;(t1{wyU-+oZe;EE}HXn+2_yb-S7)llTHd`vXzcQY+Z~wpZu(g(bxV%CKgwJ z_w~Un>$X_@XATw?MW%cNZ~S{hPCO_60n6AfE(~PeWc&AeN4t{A%kKfd=Vej`obFxs zaOcKli!WbP66Ewi8i+#-fdv9Ws5(eWBbJMoY1!gu^3XKd>g4rBrU0k6_`VG`vHke)(AN7=Gv+4N(kZ$*g8NoFk1_#;W*5(H%)A~O=g`p+ zbY_n7Sf~?wjfBa51Rf6#dRT?89BlqI*$4i)ES=IH4D`Bq{!Mr+Q^e5iJ5pOGAqO?1 z?AA}+o~8{r;rw)1VrL#Vy(k)AURk;f&e0#b!@+VjY2hvBzVYSjUWMlhF2_7I$40i3 z#>P*osJmzh+)u%MzF}(6GU${_1Lej zMRp?JR_FWl?scZFX(;KS0iPRr5!#JhvKgIc|Kn9u^Y?b`*AglBT)!H~3me>yd*3)M`fIZzkqOZtGl&yH_5# z+BJ^ItE9RXoPHbGyUV;g>7OQalChZ9Al0t@=9E4W;jv7X|B5EPnFNjCoW#fe|p`k-%8Kg z$O8A9-D*4C$@oJ_fUj#h)1YK{uT$2IFQDk0DXXd&!>4LwDk`@&d4F^3u`s>Cl$Gsi zNxW{#RrJtrb3475ZuN_%)Ro8Y7V7!{l2ZW|kPn^BzIk4b`1aRbKnrM^)JlqjlqO{> zJIi_Sr3`e6c^kbb@X7f7X68Zv)vuKm6*@`Rf6fd@8$aH)U|O+5k^;_N*A-dAcwKcTF=9M$T)a99`( z>Vz`9^FoXmHN0PqPE?np^iee3YKwP<5_2Py?7@ZI75TNnwFl$=K%-#apyAOG@@fab zZ5VJ#131D9KAcq*iKKR+Ae+NkByfJDWQJsT5LU0!+3RAOQuBe@n?0w7ai`Uz5NcSU zkZgn&WJar-HePBUu~$gW3nv+eZLjTv2H2I`&M*p|sP;%c=BVaDPnQk(l8HO|3w&I_ znrzXG-q%7F#=1MIA|UR|JjXMVh#bW58L?8NT|1C-|JrXRCU56>9yL5p66_Wj8+RZ) z(_;DTr9^F`|I;JZNgf&$Id&9=EXt)jS1JmtCx1&$yDGSiRu|tL%}kl*-=~8}RHO(! zN!}Eat>t6bn8V~L56&o_=nxx^P&4&`iaB921naX?Nje2E##lK4t!pzU)NFPa$T$LpcLx9`kY@Q$_S0b%b^55-?l z;;*=Ce@36Dh=AW5m~x>8liphs_wM^wojh=DL&c`Mte{#LDZ)h>GCd)0<3>FZ2JJ(zJnmUR#p!6e)d?|P zWEg~NB4|*Pmpn!NC(;X_2Mqi^gG-+Q`^0Air9zW%NgIlnXfP^_ms3#LS;(IlGU0&* z`D3rri;nct+LN922k#t&ACFW%ANf6636SVmlIieumm+&bGaQ6lG_R_2ZN$BOysJ*) z4v)8)N7f->HD&p42eqK#0iexf z<#$8U4JM|N*rniafvx^6mjc($0N{zkaKjC>luht2FMD&%lFskPy+-iQXz{-SBf)#?nIKjp z7zGEyy_J61O2KXjh~y|19X(CVGth`oJNn3qWj`jI1&-^@pGTljR^00R&8oN=Kx;dM*9Ie?*qf|N30=gc_zgqxDlKI51`>c2SAwXvW0-! zTqmF6i&6Q6hO-kTU*K@@u_S`Npm9=gFR^jlqaz?Gp^)JCyls8G{1V8?l*z@DljEj% z?(6g%Pvxb14zqs+MJubSsvj>ys~n4P z>UcqwtUB*W{EyTCphW*a%p|oCgr_t?lMLcgJ93YFJ>D!29^BBg-YR>|{4V-@^RK?i z^ID>bgP0p8y^}ilRX-yC0e364h|T zliL?9y*Z9XDETfZh3Q^Yb>y(DOsC;T?~w+$nfqxrHi8ImLDTU=Np5Nv_Qt*#ihya= z5J>MaIC9Oet+Tx{J2f>GN}OM)?Ihxc-BOS~^Q4thfKhJ5ic=?}EePE$njcAQu1zAm z1JFCME2S+ywhssJ?v*zHAVP}P-!EwdpNMl{nh_jCnr;C^9&30oZ|jrCvk@*^w9=hO z>#)tU;m(UiEL%Lbg(T!%i+{WB=K}vS@IiTciU>9z3h~Rm)#sgY;}iDMl(~l zQ^F`TRfmq3qxGdo{AAB19s9DE?I9Z_KV`Qby`y+%KWO(4k-&t^YryG{bUol>R$RN| zuMm~Jz&)o2Li?ZCFO`%FG#?c9vdRU{b}h-JAzC7~84xh)Ly|m^g(enK+OE;FcSm+7kuwOPrY3Q`ig&ANaGw21R?Vvb%j-8MI`5%5+hq2&GJkzYpxgA`bA`?j!C0h^tnmy5~-l-da+2Trt zi8tnf`^5W?MLNSRv@H&Xd?Fhn=gwy}E)&E$R=H%zYXbZ<$^t-0a1UcSJ$THgp<&MbJ2FZ|q#9FNF-umCrrBI8+LMjTwcqZij0G_FTvO-dm)olQ{!9 zxj>uK!Q20&#iPT{_*7;G88}W27A0}zVqT;Z`_oAY0L2miIWj*2YFso8KJA@32kPA# z5hnJpf)Z-q;w^R9KZdRLX@bu--;Su42Avpa?oTl*;7FRx&5O0v0LiYv{x$l)O36>Yd2H0qpAIa@38mJS-? z(KthXrAinAp+`MauTwPYYA6dvo5r@K8G|(`X_)4*NXuTFz6N!3Yf`s7z1nFK)*=Pf zLhSVWrrKVYE_cRUoh`CVdM59VM?RcU$~2fSvS9#3dp9?}%1Xlg!rb%giX`H|A0G6s z(v-VWU&4c01ika+32%j|QV1RH>aqhWI%F$alSeAkw_iP4+km(pUyBjIm=x|T#pPXB za_AB8f!BJEO(pQ;-}B;mo*PD9)RjG0ga*^khO5ZJkqy}8aV|AdKcC)m=y^$%)SSjxE{pJzJqogHxfC;3dt52-Q^fFyaVCh*P4VrxzQFeF_9+V;lBMR zvFLxffOXM0&Re(cMA9MH+ZQ42+9;n+La3qr%4~8pR1KA0Gk=es@ialR@96z{Za60c znNRPWVifxN_BCEDgjpYCAya3fRKkOxgO7w`T*z{sa$+Lq$w3zP zWnpW0Gj}CYleCP2h6NOz0U~MT6%`eXyh^d*j){fELvx1;(!{qGbV&ITPQTl2Yl$xR zXX#i&IQssKgFy3s@%Ho^17um9J{yFk$d~3blozqhIrQ+$kRjg}$yBhk9i7Mw0TcH) zlAnbTTQ{zYaR0-7X9I|Z9g_UHYS0bA2->AM?x8z(ZrhoPd$z`!>H^glZ6hhGBgv0R zAl#{sdqVmRLG!;wzNtpvnBhhOYCFG^Dn;n+Kl&bQG}eoSOHab?5atsVWw4+cF_^EA>eRl;%dhbNzg`hq4N1|I`lJ>*b z13i5nHbh~Wv)6@ez<{kw>g&cKoQJ2Q19U_o8!44bNR_&&&jml7C?a#&xgrBJBniJi zuIpessAMAmLh{yJ_#yGbKOhkp&;@RPKQ+=VmKtX?Gq{ltB~vO98m)dHTV1!%-i(pE ztV1pDSy;!?J3+g%KGtJuw39wPVEBQNjLifVU(^nS*E}Dc%GW3bQy}w1Q zFMpmn49Jsa_s}hIwlzvAVTselu-tI2gfpKwut3X!Mg(rEqgL|-LlALs+N3lEB*-_K zvX_vrmVV{d6N5f?QMLw^fU(|tE(JL9CM)0t33RrjQ@uk#8NrHikaco=r@WxrDrw1}1y=vy;%ibuECSt)ypgrZM+0PO3)2-X-5Ff?; zxqLl@Io$Jk^=?-(?xUj&MPJ3wHKSVf3Z~R#QL5H#7=|S$!I$bB5qxi>knom`(`P@= zvu$A%a$g}4A5C>9#S_|oDDPT|P<@cUz}wBC(~o8%n*VrJABED~M)TJ1l_~ecgYL(l z4D4INI5<1-D%C6ab+@5(^#Eds8E_tb@Fbj@kuzLlb9T253&^-NUkr=rj*d(6oP&ur zd+=5plDE)93O-GH&xDjh*842c!vjOv4}9657NmJ7$l3m=>U8L?1+#A>^-CvVIHP<;UFZblh(6_yC=4vI7KTlJT=ZdFL0c!GH z&v%JDP_gM&UO@bT@`rZ;7D4u+l{dIhni=-fdu?3*Is3LofeqTmfi(^7L33HvGjaMz z&*IYS8m)>R^-~Z&Gn7|EUSM|6)2Fdd-m?IF?@(=?_JtOkc=00P`Nw)nAT(yu6upFYM5i3uUfJ!{qZAZ^+7ayc#ta8ed&+Ji&Dv zpJmw#^sVL9=ia_yA0hhq_NNyEgv6aSX*Ws3hlJ*VBDXuxNncS`5%o-4#pA=_+eIL# zdN~t$mKwYmj-0}r3RgF(L>u6-$qzCO44x8WWnY}nzwdUiy)q0`#A8iSK+`VGY^%-l(70-El(I5N`B z&CD#G2732s9wKcl426pUq|ic~K0wohBqc3Xh!fbxB2(olM~F`(|EM)C>(zriY8=o& z7`wKw8@EvM8U7aPd7epb9H6PbyB7EwtW~P$-V_@QfG4atkArx z03UYV#ER9pa@;Nca=XbBxEs?RSM>g5>RC2su@`VitlD7tDh}O4&1(dwLi(q8ks+%gN07lCT+O(KTgv0 zFX%pJS8Q^6LI9W)-@c(nIc=f&Y<|9Nq(LeecNLWLTit1Io;e)osC_BfMHtB)sL98D z&H=?4PSpH3EzI51l)6q3RMPe>j&v6-`>yY{sA6!TrBt}Bzi-Z18Xkad(P_Cf5=F*+ zF3#=jjL?V6lmiiyBD+Eab<~^D)vpuUd87S9(LsD}e|Mqd4yN4$MKKpI!rYNXXEA+d z5>8%M(!hiR)jAK%yuO5OkjdY1RMlV8r~IX>$d-#OGf#NwZCxaZ&^E~$_t`HGk9-A; z@>Fso#JxtLNiTHI%KJqeEQ85Y_a$e%T3lX{ep^R+NmXX#!{A4JgLf**Xp}y(jGY&Y z4r@m!M7(;b^#seE{r!f@^L(YRFg0A|)3s{Z)(+ajUzS~4b}*JqGh5BQg9B?TI}{^k zwO!c|@nG6}J6)F`;S2MnB6*XC7hb9>`TS~VTF47uo74hhKRU*~T#!luisS|(giLQ& z!N@yF`EbG`XY(eVXp5f*-qNW@RlULoyuVD(Rqw*9uT*$!G=@+gd&n@QnaiKXkNLjU zw@(_u*5^IBYgS2zMnd(f({$;Rem~l^mzL!Lp0O{CPxle=l@;#WJKKNeo(d9Uw{Iv+(Wn0^w>CuU0z`~_j4WNsGZ&6UyDG`1s{l2p=im&E+B|#oW)w^{ zF$k%V61?}j+XqIM7wTN7CogXx!;2Kjdy@16qt^D9&HiZn`f$9+(aBL_A{%zRdMFR1 zqr||q_NXd&LiNW9>;i+ry>ji%KUDXo)W}QmpO(VcV#G@TLB;-2l^*UpzvBX1SA4aY zqOzZs%Kp@5y~blhGcN{5iI0-@R>F1elBwnOEubsQTj|+=CF1Ih%i!7NTXzPQ0?9FX z0G88BXs{9rHKHNeQANAm;5S&)9P6ce6&>cB1+N`C>^9ahbb9fe=7i9BN}=sgV!H+p-AF19^>@U^(#oq|?t2Jy5zebrA|} zZJ|Ly4J5n$C1{u&dzVA~w+R`@o}*Qev#s6TaJ9xO5<0BKdIM4QzYo92XhcLms7p!d z4fMR=MGaU2ogPUV7B`p#K2MSU;^iZgt)m-uSWtXWr=HA1j$pYFUI8wF=jnE{ zE%s+dZ%&VKw{E%LWww!DtnQo-0mJhb@j@YBk;iy$;~J>o2Ssw4kkp#bxfu9pCTzGxT9m|GB2r zG)oY1x|VA=bE$*Ot#aq{fuNqEMKH^rrKYs?;f9Ts@>wD$KA`U7@f|N9Kfugnb9E1w zkF>D2LNhfrRt?Utw*F{boZHa^RZQ{=!@^xa(e9GgvQm$@ErV8CdJOuOcwqgp+e|#r zm~Lu#UAy|no>h}UJO{AAB!YVBmUMYtqHU+2>C4in4xo`sU_z#h&&Cl-CSC*@Y(coL zKQybTnFSs&n}3oTxwHJC65@!u?^bIp=J|%Ou2A7+zW4< z&`T(oP2Gp{ZxFN6D97)Slv5Fs{5BaKYTe!Td5=!sv(bass?*=gQzH-0f*Jg`x8FX& z-IcYVs`JL(n;&B1^@kWGyOH!PdnI2_f)uZSu<4b^t+i>uDB48Q(&+lt?5`GM#ML$J zUl!)K!wku@q{E^X;B&!+g!6B#+7^dSdNc=d`i5c~5X;-M6A$h5AXobXFsPzQlGMcn zHA_l2B!E#+d837{tBsr;tf^eUM)q_sy$woHi3<)}r*-ff&I^I>sonf050Fhn&Bq%^ zPk<3#vRQ2{rww;$hc8Bb$h7D1+(qR?le|Ym)fS3tKb%w|mLTDt+7>fspAAU&!tfu8 z)i;Gv4zBsCl0$y#%pL+e+O`vNIlU@}=9g<(Jy>Y_wHpj75h$hIl*=ewU>Paa51Fwl zGJt}IOZ0#s%Rc4Fz(}Bc$v8BPxW&kJ#Z%mdeT2ve(q5upMKJ7p{(W->yd(PS>Qi zJ8$u*(cy?&8z##cdScfCb`;8dlAaDJj4e#LHew@=*N)z&YUjO!*)0TF8ERABv6bOj1SWV>;0q;alwa23I_@EhXZ&ctHy<9Un zh3lUg*p@r~=|N58<5z(H=|7u|Rh*BeZ@YEOA<4Qz6m1MAY^F4%+k8eR8^s#;eGrXWd*f z%Iy2nKa#m3%yMlUB776=OlK6rh+Q$@+V79qGXOHdyFZNKQr1@Xua#?##xpcQgh@bE z4X?V748cp4V0^CxoV)YaN%C4LiQqPUHA=Fw0!_vVc#w*jjCKgUGBCu5WC3pBc3H8I zHcm0NbY#4g9S`Y-f(TOuJ-rqe1!a(-JH*_ zQYNd8n#nDd$^0?BotsNnH-vfs26GlzU7XT3*X@erP`3OD(zSJEz!quISk5vL-TqWC z)t_%a#US5PpH3OUTu}Ml_oA3*s-=1N@=wVs1=C{1nupcl2TZWQFhg#Hv4d+Ed+~?x z8!Nc(ULD7F?YRXj+C}WRI=&X1KDhCzGM6bro)#Io_24d*fWt0iD~#nqk^l4rYNfMN zx-oD{{rjLa>kzRYxX*&~bD-0?xIsxt5BF=8GyUq+!yHmMYhup#cVD5osdv7JfKK)2 z+p1vd9SuB{WmRG1-dYMZqUG2A>+~9LDI_`bzVl*X(M1G|MB848UT5no@#+XCeCCt) z)bh8Cgw9Ofbx8iO`eg0IwMiwpEYvcq&EX8Wn~l&6*`gXrpcQn)(>VF2=uH`UUJ>RR zOZI?>V=$Ud93qQ=?>kSl>aO)qP1$c>*-2)_7OMYNL{mTD9m+W=CzM*(jP_)6nna;n5#iP6q*qa4R4BhGeDj*Ew&j#*g!RIOX zI`slYRO(8ti)h;zw5$K=F3(ftoaqfCec+nNC_?6L$#$FUpuL$=XlISRal?<8cVzhnYuPffaoV(|S#rCiN{IDTy9QLdry}Ge)=*IKwS5 zZX9K~L`og3X3zzl^wp2|K;eN45Nw?(3f`yQ@pSUCwg4`Ez+ziI(i+}&ueC;U?Kf= z;0ZN6E|NS6=e>sx-mcOv5|M4dlz5dcQC@9SBA%B7mCFx<$Z4=(kom6fpX z{KFUMGurv^Jhd;qw7W)|tD_|EVkdohB`!ZCk>vmm9F0e44BQp66#^aB16`Mw+JZYN zJ+d=%E+e3*ik}i(`!(fh1QC_kJBFGOCyX(r59i6Pjq**3!|8ZT2YxU7xGlpC5J8&K zol|bRT@Hja_`51(wMF_`AQw(j5YXdI51A!pj6#@F4eJPigX4D~BK&VrSq$;5-5!r|<%^J3RM#bOMO1fDDV%g9<56195m)WHb==l} zAUr04|GFZ{uSyl>Qfzq&x1ATtIQ{yRLnc}do4C_O0LM)xnB9b#Ay^5lUtQ~J`;PIeNN9R>%WB={ptXmyQZm1Ip^ zla^kXi&;^|vqGo|8&iViZy}*Q80|1kf3!CCm!1C*+BUUl6)zaEmDWEe<=rhJxh--> zWxh|Pxo35PNz*3mj`&XKFK5*$0ldc)2WjG~u2Kr?r-SQK7mu3MU$tZ{QXG=I(tkaW zp<$_IzYobz!<5>0& zpV?0gRV^E_FXplFc>b(84^PR0-yo%f++6=725QmKJp@IvE2KA5T=9rV_3 zhr54aVJOIpVl?D_#|TQ@mCZoRl>*sO(tOHJT3AvaIGcXNjpVz&j67W7M*2TCuChDa zSTIY!pH)NY?O*fjee|v@qMvC#@7;HkiYv0NZ{L8IvI%oktbNBZzo}c=MsQbeCT8>Z zMk665QcrKl|Cp`Jno-{VCE9;gPfv0wY-7jf2K78s<9mA_3WLK9aP<22>}URin*Kt^ zEde8VvlXeoW#-%+(|XKLJk#*q(sPjfHF`p;V@KUzTrMsptJXEv2;UZ2<44}4d8Y?E znJM%(r@Ci1^;9C=FdG|Sx{J`8fI1^xB4ITr_gyr48HsBJ|@EBgT2s={fGh?zxKf*b(|v(J>R3VOu(mTu5>60od(bYc~BN6J1&GMqn=1 zBaO-OgdyFMw?;)}2h7a|#je(LT+Q&V>Mg5k$Vut+L%E3fAht&@%V z+Hwk_=uZ}tmcqnUTe{4U#*nyDQmJm+lbGG_yS?!qG00jmlmNII4OK{HQ!N^0$_(fosj z6#%kg^UGV1v>PV}6H`m$hdK{{m@w1w%<8TKG9=hy|KfjigR{a>edn83>nrmD*YA31 zuK}4}1)>)D*jY5-Pxl`bwAGC;y z9!xV##IqMPy=k+}UFuS~30>-138)g{3K-&IO1Q3k(_IUFZ{{4k$YSQwT@Qi74)!q% zrCD7!L~GUePY*qAOVxfynKH;={PAD>8L3*y?{x%GqzJgqN}0&nuf|0}RO9=`du zAeVkmPpk)-mp?eqp7{UO(0bn%bLQ4g8R#|?=eg+Xz02Wn@4B3|U4m`*)=|xjqCi)| zWANsK^421G^|Fc@^E>kABUx3}v~I00Ea>wwbv1d;ey#IBXa1B8b>2YIP1+UXJF*`e z@X{|wcNhqxbDPBe?Aqr$e<-C_V`gqXByXM29p&*6^;d+3(cLSm%Cm*|ONudurL|>M zCD=kJ^z?;gxCY(`?ikSd@zYXR^uyo0zDhzHIWNPC3=Q#0tk;iC^rYB0ytR$dm#YRWI#(z5TpjHsW~}Ca=%hMboZLEYagm&1bcOHL}uiy;~rOKH?~Iydvq!?B^ZM8H`W~FiKVOwFf-w+JS$Z|)DyITXd~h$W_cha)0z;gJDZt-=>OxG<+z zQ3~%SWMb@D$t9bTuJX9H!;nYy&t6Jej`chFAb%kKWc_!QJ%1og_cM^kxP`yWGh5Z9 zonNsqDP~(26^j}_gL~yLUe(&Y1dD~#Oh&XYChhrf1CTN|RppP!dUk`yY9_&y z4Ut^4b4-g4|6PIk(pnhVZTH7f?mK1%<{=Eg=h6O)SYHQay3 zKVK@f5Oh;K|7<>%FogNA2mE!BVs@K~vvC4y3*iymv6w1~enQb#E#; zQU+6;SMrF|VZY5EuPLck05E*{6GT2IjFCG9Yf}71(~COdp)hmsCeHl-?5bj4wtQ5{gqJi ztZGu=po2VGQUx5s3mNB%nwTICW`#6Ed%kIJQg*g9y|Xn!8LYRhbc?>(kJ=;zH_$`r zbZX~jg&CsFvE<1O7p9g}U)h6vdY|e)-RG(@-Q71Bh1EP+k3q4u4ZjW6eEOGEzx3ER z&OP#kESGgHD~!-J-Zz9ipBF3szg)o92Jh)UYClE-+tNY0;BzTnPb7?V`dl*7`3?8~ zPEy~ZCSTc$k|6>$26uQ3&U)7y&z#E|^sbwjn4nO47^QjwpPGtM=3lgUx<#Vb0~YYaWr%Z#w~0bVkffDRcqQ!a`0q%fEp*NED; zMGBrvw3%>`-~)v&YYv6%5fE!qC@qbCKPsrS*`nTax@4$dh5{_MEumHy}%G9@V)bU1sHpPYS*4dQzD?bLPD+u zV07L_n)x0JnRLQMvnQ^tYVuOfFcC5cdJaxtvT5k|%H#A}>)?^Jy+w#ou?g2UI{Xz} z!&j9sHOMXZ^}#N4l7^j$HEqeJmP8JN<2UKKh6c3Nl;C2Mf%m>e8Z@{fhF3)0;m?G{ zdda+L=N%%Il>xAPq}-GLi6)J7c<^xC{YHOEhe3ZO36)Q~SG%3f9;G;MaIa}6IWc^J zq5c^SJv{qo?SO*A06uGrCX`pygdTxKl5&G&643QzCT=&kg7%X1P37b-i*M$%z-Y_- z@S9x-iMuylr|RDBY(d=C=Q$SQI1W^O_wE5i5Q>1%mBePSCN8Q+VZf~BlvF?pqK zO&34jwhR};#i_X;qfUny0z^zXFS_{c4_Tl{Bq`y(DHgQX)>4I-h5hs2IMI@CI>ttl zOiPiF?5N3NeC@MC@6 z$dt5{mcV`u{xiZvU?M(fh)y}!0E*B+<9bG1NN^-|VqpBANX*wfRFQzRM&Rl#u%-|H zi;5}V4t#L@jzRBS+u12Zd3NCf>lx5#L(bNnx|!Rt#sxm=_5grzzY%n~2kM81 zvb9T*h@$++6UW}8{0OROLd5n?MsS~qlhoflA5A7@0ihVvChZ|>Bo-`f-{PxJ?ftl> zovV^7=S#{Z<3$=0>(fj5AIdN6tKzTmX2d=@%~HqyV}Y#aLGfm!-qSbT)oSPclSmkb zMt@qcv>Et$(3faSrL3HJ)MWbVTOeFN&KSRT!*lT<{GVw2|eD+I`KHc z0w|F(W7O1=VmINonBEEvMl0ujW#CavuB?ROv7R<31|Cc|w&1NZ5-Wb$@lQd9j=cNF zClTbvBYOCb@66J_-lY|X@7Ek+Xa;>4iQg-+kM(FUi z=H?)eMd6wD_9sup^L4Ai&rYXN3o|1*8llvZYTUj1%F6BfPHP?oxXS6BvaSvy;zB^S zf-{8{9VdCGvQtgn&0W`yUEw-AmHcNbcs&>NlUF_D$nWCn!ir-$`1vvbKR=UB> zEvLDk!Kgt66St3IB@4rN`{qvtS}ZIqp67+QF{SHCbsVb(^)XJM39TgW`)|)hG9>HJ z`1q498ufaY)J7>fx_9=~IrL;$UT_xKaqLZ24qo)?hbSlxSRYvLj#ed~_fdfMSx zXE$@${wK{&>~g(v;D+;rX!8&uA5KNo0)ln~N%%N)9exzgWYC9W&GA%7l28tgQtIWFRJvE0rVeEF&VC^h9H_A3agS0oc;#gI~jhh`89R)fi z2*og*4s)694;83bw~NbG-+ucWl><8HmpjNI|3 z+V@tRj)7$2&;^u(iYX^B5ImKnuEb!sw=z=G$!8%@%HDHwy}P_%+hV%={�ghjvUb zCT!zG`<6hyuBxUF{u+y-dG(`zxLfeuE=)|@%X@n@heilUEluKfu4=w6fx&9?CsIgj zB|-7>4ih;ZqY#fZ&5Wd~vM&$}<2I%1sS1~3M&-yM149|+D$UQAR!-BR`(^$7ux1p-w>&&gNMHum>Vf?m~1TjsQ5T^*9P?^zA; zbLKu%30AwmpRAmc4f|kHO!Mq+2+r$3#yT?q|1wLah8uea_hKM<>I{CjqN1i;#8JWK zz)+&Z#ESSaZnJ)7b!}fBTVpjNdC=^9A?7IpO3{Ats;g847LB{~2F1tzYodXzonw0; zI?zl?1M==9UuUx&eTq348<_g=7oCS?Sqs3$88=rVHpIAX+a)%Q(#Z$)dTz==*TX-S z3Pa=UG>4F$d(-O8rekS{*pcB;)x0-hGdSIqQh!|Yny0ZC>I-@u+j z*o>sdxM}~sYI2(8`pTwU*qd4{0D{ClBa{6tD^x|kw}0|9#G&2bMwVJwRA6)C=T7GS zub+k_A2WmcIWYUKK04UAylqcy;pfH3dqJ)ujJu9ZUWiXk0t`G>!xIJZ^K)ZaU=RR{ zyRCwW~Hd7*p4?e+K%8sgF|y4*uNp!912mNg58%nVJ~cmj(>HG1hMPG9B- zE3ew~j%ptgVo?X@>a)fWTd5KeYAE|KC;TA|W#PS~?`R(pqHtkK0-LM7V_7u4uN+X} z(=sZf85GwMKZz%a*{5wxi zDNSMFPOe=iTqunKUyeUEvhxO_9vg!m(%H$T ze_vrE1-9Ux%|JY6KmNFcw1xQ({^`_Z znUJ|*Mx=ijf2ohOb&SQ~$}l21emhxS#LXWS-6Fj$|JSp9N|gj zn`Pdcg&;@GEIL;V5ZYZfiOHNZP|loq@E^iP!EJNuBUlCibwtpX)ExkkoAsolc*`nP z-$6fiDoi;cSH%YM;s(ajr$GkK+ha}R&(~>nW#~yI z#9?GB4DNWXaxbjwHKjz`Q6KA&FG*!`v-L0m5Il9(RZ>9m+nr#;=II#7no4g?&@#aW4!43 zobs#vWBbQPwHgGXe8$EbrGVcyH#g8EEIJ%xF8Bm zjTU96!#KSh+&9Ja#d4GZ#&%@NW61mSw6mO^>W-v)L*{vBJwqnJ&P|KfUyIMFs_uxH zdPr;V(~SH6mkW@m7KvGw3GEkKSC1kVs4E>b|JWpx=^q|X<_2y?hv_8o`RxegYk1#- zJONE(hni;>bgPTq7;_}D@>!sN4Kq|bYYa*4}vb<@>v-AksT~jh@33)c%o~y@qOBt z3#Dv4)_5=;7MMT;ou!x6{rzc7;_Il8N;2gqbTUc&M-Jk4mb}|7s7n~%EjnZ8ZgcZo$YxEfw zGj%L_q`0qQYirH7G(g9H{C_xm%c!clFkBQwL6J~OBtJz!TDn_0q`Pw?DX{5MqyF#FJDR3uz-?`_UG0werjJtm^Hfyat*IF~)_j#Y^nWAx~N1xUQ2ytI{ zdu%5edkA=I+-gvx)lsSWfT5ciPFl`?<6>i&lMu4}_4zdy6tm0$U3&i3={f25z}=Z{ zx2Zeyu$&lA!Q%FF*wjc8GyZD`^*5(iE|p2xR%~%^&vMzdeCL`CNyx$jkT3?F6fR)vuv>lTl5h&BQqsf-|=sAR>>UU+2(lX1s+hY}#uE`S$oP&O| zvLQ(N!bhVRg5Klr6+WhzRIFkUJy_l&gJ|*H#kJp{aB*Rkk0kTP$Wk3{#@b2bX2E51 z_KK*5UM^*o4dpJbhm5~X(~A5!%dHa=6Tx9_5Jrixua3bfebmzg5bQ6Z$kh%mS!vWb zn`&pg@a{f6Jk!S45a^G zZXEv;`Tn1_9iQRs!MnRV1#QOPaJ&SWu!GFZ+?U@*=q=in2-xk>YnUE9UMg>d zoX;(H^N3D$3*LBfQCSoIA^B+Ix3?NcqT{*7gHSNo@-H~7r&&QvyOZeQ1k*V*y3s1V zZ0yuevYWmxs=ubOjBAQ5$cUwlrAuHrO7aXycH5jisG4;KnK7V{GedggP!`@1Du7<|m7q$-a92+4u^)jQK}ROk{#)8j!5!nZK{GSg)=w(v#s@ z`_oGjb3Me%1P#JS)1A9K5RVMf8|HVOGoy|y zP1ybM!hwq>1Hh?H&lmsjB*`Qe!-<^q za-eJ~Iw`jr@P*^!#|}4JB0R*;&Ss#~{LcNw@>9jG)UzLw-u>9aM5So>4KUf9KU-w8 zwE`zyeCjXEbNv8&*V(rIyzN(prTx)U8WZ^hHaGRZi;tRaDR9t%vUw}nv^53>Vrkl< z>|hLR&%Z6)hOdMHjJWB{f4RV47M?6CEzQkatY74;=N^xq+jL#=cfUkAEbJ5r-BLVy z7ws6mnUlR547S5uh|bL~cxs{BL?^2fwkoRQ9_JU~7Mts)N}JiweaY(G3Ht3iJwE`E z!C>=L=qFLs(~PVXL$?>V^!f?0`go-H!nPc=EPa=Fyr(<*<&Fhqk*#In^Wzo0_#Cr< zD=Tn)NlnD{iPmBZhV4+3NVUuuM>#bj2|Eu)!>>~N$qf1ggd!z2)P|X_GV@5rXOJPu`%uVzLOjR~P{ztRg=5cyiHzw8QpH+Et{j!^HzJL;0=J5r8`yQa!_v0_%RVKAH_GyU2jl2#{{7rnHe>8jzur34BhF+qse}ESObwzI>MZdL%jAeAQ`} zs(LeOo^)M1A5_Q8jzbfc zLDFyuY)%B+Z!3T&Lb8{>EJZ^D-^NWs@(m$NF+A(`Y^&Le|HQ3z{K)(0?BwQrR=tPL zGC%Lv$6fU!?_{1&Y^ri7*Pa6BeZyaehgy=*6#&G#y196{w+)~1%%TjX*RG(sStz;C zZ#>&EKFi23Cm@K{J7}-}yF)SWdCGyQkdiSjyBRx*Z`RvG2XgD>vzxQOQ=x$I%Kx4s zmhmre&e1fw=m@YKZJaXvwd+nD`!IfWF>mL@NY{fG5gOk z(NULebeij7$(~;Vb4dUTW2H2lItOFwe3XNo-$E|bqr2zCxj$a|)#S(s42#ZcuCdD9 z>d%)VK`y7m<61dhNM(?50KX%G^dwnQVb*mawuv`GT8_KM!K7=x$xDoBLB$B&S3EkA zN5jSO%!jivH2zBEq{bvtKB2*(|Kh|O8t)P?KlsF4{aP;)8e^u6XNDFQPr%}e)vq7# zGGQ`Ee!XKF8)zdcc-fjgl5}%n+)z)o31=dGAlho{p2Ekz7!Q|$|7fyxn2!}ecyt;E zHF>K=C-qH%@$c#G)Nod4(fI+!?RqIV{|o4|^##23gb%7CP;3*}dRV+i8lnG|r^>yf zJ_CB+b&*Vx$NqrjfR2XF*4oyLovf)U^t!k%LB`PVlY$RolpyQ4&S>$)*kam~ZqHf? z?Es56rVE!+{fB466{#)4*KYa~DDqq|5z6@?3e=%@(Xt;XGlzXtUZ-9uwxViKumS!| zRxwYuw=KDC_;b-qMU~8mZkiaKgH|SQ!1Ewhb+fQm%K0;=ciQ6+J|<}4s4~$5)xJSQ zB8=l zhlcg!>iLmY>+3aA)75^ivRJ@kzwQ|c=R4|%D?dJ)a~YfLKKSS$O=+a|;%5$lHEEif z<&FssZTM}26$3v#4MSNNXM*gwhqEZ!$&;r~!loON8);z_BGlo;-6@KAnywEF_X|^_3*3f36V&vrgp4l9&IkGGP|m&c`Q>3 zap+1=8+*-qKdR_4KRgIFTjMd;jhMIo>$<5K)qdfwS2GxxcBu2i!G+C|^{+?$>((!u zr|UnUs-qR-iGp48Rr*DQL>f)d56Wxi<8(-tY+4x=6zO?xnh1Q_gFT>p-d%z`D4+v}X09sLng?%bg4V`=XFI{3UrPam?^ zEtP}3?>akC=vc(-2+H&R+a=u|i%@!*w=ccUPeW)d)^Xv9x{Yol5{UXgS>{%_`rkC@ ze|@~5Ml{%OK0LxV~jJV>so{<+WF_llH`Q{eGU76SfqHK10 zdYYe~Z^N~gBY{XI)X{KuvT+rw2(L1y2+^i%7+W-c(%rC4+g$#_tJ9ZhTJ7fgB0Pg4 z-~{^#;1!QhR6dQiUdATlcV|43{pc2Mqq{{tG#@D*ifoKCn63efn6sq@%Z6i-GhK^rRyo)eo;_Yp?!0 zJA~`W?rpfAYl}x*cS_Kn<& zRCazoH^WbS931a&xxd3-z0!s(XR0ao@po938F=}|RSzzCC^OQjv$XWA^$b7tOk$Yj zZ7U!P#Qn@1)e4h=Nz3kz*X3Ma-O&|f z|I~hvfiKu|hn9O+{z^%RvX{4g*Y@5|yDmeD59lJ!;F?F2S&95$YX6noWW{CQ^Hou- zJ>$q*FMSMhbrS4(d{b9gO>f!@U})Qx-|e7uFuZ`=fD2DogtB#Y9-X`(*LRs*Tl^nb zK&^diTO77yNKmcwKwlnzMN<)Hf2CosJSYl3G4_{(ab4ccW@I&IjFCgf3(7?`JiK9q zJSooM8r$b+YqO2OZenE5;|(p#&J}$Az|O&QIfME}v(jdZ6dUdQ>Q3yNNTohrw2^nw zL=(N$NuEd-DOH7-IU7UMXFOl?^{1(N4rsp^$7il!3hf9Z;R9?#Cf*?Jw(?vtyE8UyDji<&O33?vO6v)Z*+$=lbo-7xMEzbCiuVP?4lFy^) z@ciVO#+kW(*r=N0I`dgyh=4m;Cn3=FutHGEe7g9u7vA8yvpqhne!WLX^4#Gz^M0t`wQ6EV?=TX zEoa`A6}NiQHfvu~nPI#o+bi#fYnFp7!u#R?yc9;AiM>hO%98|u1zMY}&b$6n_3%E! z9nKeXLI{qvb-+&?-LAjP6NHnpryHVX9urqt-Wp>MSWBztt#tbJw(>p#?EP@zUJ`*A zJ06;o9}&t_VW+2#O{u-VPufRFOsx|XX96mmE7g54#f?FEz2vLOXQ4svUy+l2@{}1J z=pe@5}w)*E02A<2@$xG#CJr zg9850>}+&gKLE2-Ml9VfDQACCxUoYLF*vrz>)e+;ZDvzUk*AW))O`SUlyoi8OPi}g z+xz8p1Z1LF?=!UDpw!{$jEpO_fEQPTD!R6Y2b+92z>B}Ak~SAQ2JNa z&DH3N*|gToFtYCv+T}LWpI?y2nZjy4mdRdFUn%;!Wty)d1mjh-6!CF3Sj{AvB-}x` zbG=$bXUr4uCc_liucb?8aL4@Qu-;j)_0qP`J>82l20%9CI!kGQPBnf{R|FutQo_2a zN=0)x$=VuQ2GpceRoYf6k1d%LkiiND@wUoFs(LSP<0WkPWZBN6o*(G^2$3d&v*b+7 zFfGftjMB2N)iD!vkqh$+ySwR*&yL8r%gNj2bB^ad#F~N}m2*`p>`g_CKFaF}o0=BH znzN$UyNYF4yyT9Qz_cr_(ynl|{T=7PUn{PV6E|$(6OM+NZobOTd(&-Q!ZS0;Y@0LizKvi<4Ih4QB#W;vIuG7|JXXNH$=58BGs zOApOIkcpRg0Qlb>3-*h?;mq#WdwRFMmbi>n_U+|U=TM9_`%kVWpvxhJG$S<$t?2n| zx3rxt6CF-NP}c%CS4*%`CXpwmNBAo(S2okj7m()ZlDLH`<`ZXr|NUYVzZ<`K)+x4K zfbu$GA(VRk(%qfUas7>(hKkmr0TUf#-r75Yk(!aXV=MILDBW4z#F<3W%1-idENraP zvrO)Q(~t~lTRlS>RsYS!Y+-()pd#u>_z(sebi87Fn%}yQPxT{1z$f49hNK#A zs=HMYlxybzJOPr_c-v~XTCwY?6VPFR|yHsj}pog)-}v=b1&aON8#3hoWgD6_c;65U+k7h4M$o!QiX*Ur;-w$EPI12-o}ceLXAHJQ)y)IqmIJJ2T(ir6@vsd#?;S^k>X;>< z*Yl^fHlDj)q$^&#zYnm0);6glQuaxnFA3IrW5*jRAPR|{NPe9F+~YZl8yEKk^S|wZ zILCXwOm`!$+AXPybaksH6$F#3w%Y4xNw>2NEghv?l^p7CFMAfuT1~%$Gp3T&8ZmH? zgxrnXA62UrzNM$-PmaLFe>qiPnLcSH5bfb~o4R~Q;%$^NU%9j21)p-FX%xHhfkl<` z(9p1FA?;(#0PNX*j_XQG(?9SpDFYleMKGNpBsv{Hb=NI+vHybs^-}(QVc@y_e~@AS z-&wQG=18PJk_badIt&L*%*+V(T^@Y)fl8W{Y1#rx1gd|LT-ncl59DO{@+Ynd8_)u~ z$X7_B0iw*}z{>WaM}&|82P*`tOf8G`Ck9T$S12EPbUYxuJ`(9oMS`&OH1PQ9<&92u zr{9ry7cxbV{ILRpT|5Nz>2a7L0#lCm8N+YhInK z>cj*jJY`riu$748=4TC+jFt*?58sO_`A-@zBr4i`C_L&a4-cp5e*wgoQqO# zh)4F&(jGkx$+3~AQO_IIZ_J0Hj>*T#nk6FNy$eoK#L4(=X9{e1%MtL!cZ;&0qxwel zt{%GFl3jDVyh*Lk?U^2FShNBIfT1Y+@W8IOt2>qs|E5vE^^us!=Y_sWjBQag_sQ-N zn$4G!L$KEDVfX2kA@&M;S|s#`e9>BRo1CxNe$uwSP7UmcjZ!sPC-6tT(m$lHdQEF# z&qd;eMz=atmsuotT_?|-;QIGZoWsjWs_8PCs^e6{DMot#`x7;TlZsK9GR8RI# z(2viahrGv+W;bj>w$I2d&DEmE0c*Uu**Yj)^889s%JCC1l(Kl#zp%B`Hbp@}X;7xw zEKx?`vn^!%Zt#E|EW7=k|HHajgtkNQd#q9pJLrmUtBWa;;*&I0bfmA=e6P-Mo_wlb znEFG6DJC4D(8cq^9}$mbzOwd$dukf=c$trwPwb(eOa|WuUs6RXzV49vgEanGI_=cX z!4P2b9ssi^jh8RNu1bHg&KQwu@Aun;h1th4r?3$7DOPz|BmrKu6Z&Zey5N02rVZe3 zM+h)x>!G8C!AP>-sp=>x(y8lN57httawhQiI5<+^c8j+_tv97LWkVaN-j`R0Xa<=m z>U_w?J^%_;TlZJx%i8TnMYF{h!68M{)fo}dMOt-9F%2dwfSYWtG*VdE6%QpQ7RWCs zm@TI)EX@tRn>m`j7ai$01`}Z8-6 z&UV0{AfeJGZPF%LfSsAf^f=ovDtnW6vQ8pAgHC?A?@Pby)6N~J+eK<6W9nxy<|?jQLs2J{${>fUcnIRH~^*8Zes!YSm6zxqW$U7$_}~zlWom+b1>ChJxE)W5vE0rr~0F{Y|Mv$bcW7IKn^`4{FPuBf>E= zz<1|IDJzWyp6^rikXmsGS9ez}a|cI942)w0$#eNmeShqiI!)CIZyg<=a{TTCLoF?T zQXo6)+j7FO_Ex%8$^>IbvS;NkUc#@$=A-_UiK1e6!I^&9u2EX1A>EgBrf3|$j3%Yx z=F=%zmh+LRzlA7ytKZV=4mH^#7>6MhI#?1DLp&{|^_IQQF~`)Rqt#W^wBq?C zz6NlRBqzj5blPRVl`~A3<#WUfI^^PMc~H%GUx(^{<}=OuaDfiYMO2Ybc$YXL>7sCg zs?&5)rLwZLa)z+RNlT(da&XYkNq4KCG-G}Nnh*n4w$J`p68|jp;-fRhQZG2?KXxXy zrYp^`Oz>D7Fw^K<~m;@DLBSpD_A2?zuP4ER|{tO(FHCx=lhqcewLNtie zRB}#tc=V>BXFO}L)=9&O3lpU4qW!GAu5q~m;7^D|&2rfU|XGzHYUCm8!M zAldbtVW6j-by(N{9}-N(W1`d*<+?jZpqJIcco~x?k$`SP&%FD2 z5?A905WdN|k`lEwUavg;f>JMzv9Y%U`LOIvD!YH+7=-bp-Xj9L={8H|{r;aJl)}n^ zrtvmawcg}L?C~ZG+^HQh?toU+x1;r8;~nQQgE7831v)`RpdyJRv|OEkfg!%U=PB{v zI{A3P^M7Cgo}FQY6U7tT%e%~;!*4^yop(%>l*h`?_)NNW*?KX zcG#4hm#~UAIN7YbYTIudcsH$0`vjNEJ@@Y-77S-k>$i*NNpI%^_MIOR^lEXeO63w6($hUgDwl2-D^q<#EG{z3p-St<#`Z7d{&o>f24@01ax>HGFZ2 zhbM?hKlUKX-Qk35^Ia_79LLj@Mad>R?r7--s01GhBoo-Y-D|V%?<-WCIzs1!C>JNk z^oLfO>A3xRxuOKF62~5&iY5aBhm6b6ML$l({xvs$ri0}7jIEBOZX}jJ}__DBO@{%;Q`-u&}m7NNh@ziMjpa zYCM-~?BU1Iusu`ttNVMGwRzpMbJrE4*mGtpQ8UIgA6%H*eV(OtmQY{+CRx7`*O6q zW$(w!a}O`vA{UChr~9bA@7UDs< zs#=k=ZjXAl*|W7(PJwoM?$KgTR2eCi{gu~D65-FvG+#Xp@3o#OZe$~=LOgu5Br~>V zK%G~ps1cCUr=_U6y|&YACE}-vo-JiD33!FWS1HU{N-S!&LCJiz-iV)FnfWb5&UuzA zcFH9)XPK?N zh-Q{)U2~YfE$OhLSvfpLmAS42j71uT<->}T9ipVfBr#=+^NE~4?Uv5P1cVcG@zOm_ z^pnr35a%7RfN`tYbqLco4nZINfrf&T9#kOf`{WnVpjylKc11q}Q92_JgQZ_Kn|>2l zw$OY8I`uE3zv*}VLB|&iR;&P4`w1Uw-qQk&x(e@P8#0?hu%M|i!n=B8BxTtX3Sj)Y zD5^n-M(e&@2KO`aG96Z$iTUlY?p96RtF29AG{tuV4D=a2`+#HvF>Sqa)-SaIG&1`1 zrH=-)n=P>^8D{c?wV&2g>wmKlC9zu&T7KRLGyt1V95n}dNhNyUa^KkPH{vmBemdLs%!UuMyw|~@*{Kw1MekKi-h!K% z*ciCW5|D`6BYG1YOSkbsosud^s;Dr>G=|ldTQ6zv+O0iCT?HgP0@?c5J8koh73h8{ z^oDja%!C;2l%`&da~LVy0FVQ~)=MNuB-+ra*4-&s0f;X&w z{W7A-;}z8?;@lDR_|dy8HpWhj>XWH#Sq>;|spRyK!fM^CA#4KB9HHex(JSo;g@v2> z*6+G?UlW)(&hECf0UyXytv?TmrnuNXH{!?1Qq_oTZWzFk;?r_#0iw_`hd^E!`*N2; zbChMgfFMwiZ(0QoRMfxPq5pO@e&z)<`i4I6xm7Y|Z- zvheM&M*h?bKK_}Pcdceaj}`t1w|kkc9nozyW>EnQ!;skmqzhuC_s_5fUoEgmf)n z14p~htV}%35UD?1DNPg&eCuN=bbrOHkz{FUaJb`6o5g>JKLVFowC7@!#{##ilL-5F z-TkkTZ|*ouXtBZ%kcwXd7^0lP-p_t@svjJAqjSeD0`DgcFp5(8hc+?6mySIA|4uxq zPaNI-sRpYObGJTLXld(GfOpQqy0*3R=Gnqqd>v7!T!C619UYUivjb~_+xl2j zLRWtGudB6@T}a!IjYz#%hAcV2e+pp$ zT7hIodG|aD77koSsKSPbk3mRI|GdA>7!?KbWizsq8A~EZ$)S42)p@RC{pkJ{b)GzU zCq%row#D~x;-6GWm+{>ZZW1gM$b+2s9@Bpg9w2+Kl|R7=`gxx^spj3+n$j?|uTDMUozb)=yjuNy_O@PV{J6CLmM6*4+t`=?M1B!8X3~?F%lCsF3{Zy#^ z&5CbT9tb4&?gj=_fxICuj$WTwCet@_$*#t}Cb)NTm^0w6aWg98hR$tP@1Bw};=KnH zfpyEUDO%-2?20)QF)UbdCyI5Le9_r~z44&O>(rF`QgQm7pU4`B)y3bdzkWv&HvjZ(1TLhP#r081M_&T88*a98;aYPN z6iQE@lY)_`uOl7_09~ap)4?!8Imm?-Z(5134Zo;_=kmDU5afPvb0#og4i7M>W-_P@ zHaAlD23+r~kUbs>yQMlBM8{SCvCeO!G== zn#+4jUGWym9+3Li)bOzSWGd!W0PQMj8Z%dO?0tVP zXa;e2Y{2f7=Smxwm0Nuk`!&_=+i=%0!xeoSvodZ=Snx2@=;wse-1PFk!0$-Gjhk_< zTCh1D`=de%W|*(Ohl^V5{(>mx6q#jtZIhZc@f7FUk7F5@(x(qS3abJvHNM-59CD9& z0QS2;QsLOK%#04nyvN!eZ`L=$DWR5ojh9@3$L=KqezYoEy~)KE-B^)Y{@h?!JkWl>1(0k}_Mhz{QRfl! z5jZv2Mq-cypgM}j{K;|Jx&?!f3QXz(y($_y-q?yAOWZ04TO<3P(KXFVqg_hltz^8r ziG`@e{rcp#WG%A#paCr?J*nP7248Bv*Ep#JyRMbdxTX}6%;8v>e{_8+r}-q>VfhsH zliB9GZv*|VN0LHw+C!OTY?y3n^GkvqiS|Xn9VT+%p!o350zxop2eHiUA#~QOUIA$6 z!8xhIjeKNGH;|=fy&*QGLIui9GmNd-3K}M}+miR@5?4P5Ud4Wkm7=6sFFiqB1~rFj z9meQx{B*SzS zM)BMJXmcN3ke8b2TBoDtD31CAB|aX0(AVFE=X8NVoVRr=;{=(kZecbG=R1t-f#gt7clK(uT*RKidVA8M9 z%Nn`3xGYwCQ|0(@_IZ=Kcw~gvGYwtpk2Dr9=k#nY9#NuPcI*M^IL?kOETECK&Iu8| zNu;WzCa+H%p-^tKJhsAW0eorM|4wb3xNW~MI2t28t~fY4y4WbQ1zPouJW2h8LR}u) ziE;)$zBiO7`B08aB1u!FvhMm%y-w=tu}5)}o9nD3q+r(s2EoeX(W$btfA}|Jc6lQQ z(#0jjw3uGY>0`SoeZW35rk00ENjW$;&x;Tx0dz#f9dDwl? zZOFZD;eG)$f60ax+1^>0H0h$@Hr_gf)`d9SCMgPg>9HM3iU8kf_pyFE;B*l6BPnf! z7>lGyfxLHssgiNj@fiEbZZf;+0G%EhJ1u?l>}b%J7LeIeecRYxTUuF~Ai9bg6gjSO zcdwz&NW?xql9rSv=1#Z0enUqo)c-%Q0B=EZUS1m0Z@au_a{|UJVhUxGvhy0j1jgc6 zbc~Gb%HKFxL$&ye^=;!+wsv;-OzduF>K{0BR;@&pA07thD0k4|#mIb7yh$Cz0oMO& zdW?AJ03ns5a&U~51n^>x$`2S!TT^p&ZB1+b@fS@k)9o$2q;hSm^6;uy9mcFj7T-e3 zlvPZs-_uuy0n_LYT+x4uK72qe2=sdRnt}($1(SZsbs&4W-_o#6?OI*;2n}tx1cUZy zcsw(^`s)&%r}@TDu~q<2zi7U!08m{j>9Z#?UeJTV4yLKrxbbD_lrws)yT;Sx~S3NNv7MZ7H@$i9{284P{4RFAn`w-Cs zDMxl!XY>c<$MoL%OmRDz*66gP>nm#`K1Ja#NON+?Kd5dT9!e7hZjlU3eH%WceWl-p zwBV*-@#o(yCgb59=*8^U;(d4aR6uY@t6FuHie46xabvbgnHPZ}`y}$0vC4#BnJdj1 z>bYB@bT`Q$>(=(Xnmjq@n~8ZDD*UJCP`NcO5;R_mGsaYvDcxGbvor^4KvUw#kdf`* zBD%2E3JgWptan;p8A8!;INl|L0__)&g8&-AIp1FQ%8^-zTc`N&8`{~f=-l#eiSm_B zP=_5yt)7~v?%l0uj+W<3sSJUeI2~FtK)~!+H}xl_840y2uxsDINx9o(m`+?LS7apg zez`TZ+U&>?xE2@O6Kaj}qCu6IvCg@kx_FA}ZOI)T+wt(5?N0tUZuh2Pi1-?TA8ElTWPoQFWG%iEBCn7nxf_mxMHJU5aVkJ zQgy*Y&>@>~{oB|b0BK0AqjIg~CFl9%PYW*`I90){tV|>O!#g$GZn1eeJ6aLkp}w?b zay$3*On2_larcS?NV>IbNl$TD25lTC_U5_r)AW!p+%}41!$BtD!;vv5mtr}v?c7I) z-6S)1LKE;!wMRL5TUg3GEhe;c=}7Ko$wFT^P%^wQV>Hf^>T?Dtw=z5~U)iYHab zfaTM+R{w(K2#Fb^H4oY4J+REt+@Y63r|ejcwlVxj;&__vE)}K0>*=3%hQ~7pA+vQN zLSkkGZ8=cDHmGu-ersiBOxa0^KJ;S2KV|9{z>0;BQhoXFrUc8al!lsrtHeSQzW*l& zB+_zDbsvsi6Bb}2!R%vZ?_~d9nt7j<_O6cg;DZzlY@qgE26y*+cc1@LU)=|K$uMSe*uJ;5S$Zzx|iuis0T~3*?=ESqly@tE`G-0gZ5U zG`Rc6;M==KwD)n@`voJjDa*7>1>h8R}|K+@QDKbF#l`_nb`roL({kJK>`XQcFcfScl z(@5u75cYAe#f|$Dkc9Xhzx)R}kUkhdzE7~J@IwfNSi94e{s(3N3M+LG) zz9f}`Q3X^p(s1!C=i=7!4g!^%Xu9Zo@`2q5-~>ptiOr6y0d8sQm>~E0)I@OWqFu=a zxK0YD5R!jb*UZ9S``-?ui$%F%ErJZG&F- zeRGOW2aS-24J#l>?6segsw%_ymLqL+=E23d^~`T1!T_d)ywUjJ9-_!@s~cOW!*Yx@ zca7`6XOgM-q>3)vaZzTh&KkcqWzhtR1%Ga{-bzw8UjY`r9bo5d`05p(Kv90ZPMVrB zD&t%FcmdO;AFFv(-$->;lytQ7e)3q#7$`8*n6;%fk9{c+a4_{{29u z!f#{@BrGXR+8U6qU+pponEQH9#kR#(J+C=je}8%>Zp72G{vJ%4d6x~S8R9{lT+Q>{ zLqjGkegUXKVz)^s`J*2J-2wrGzf=S{k@oyq&30d?+B zC{#)+4oVZGF~iQ&Gu;bly|jbe?K z)XS@jO9j&jC6dMoW5V`@MNw4X&J;g2W!(9pwwCLu-)q=_kn&pw2dPlFypxy*FJ^rXiC58bHU04YrYWB<^}KM=Lkz?w`0Je{Fef0vf*A2Q)mm z!`Xz~KNBL6M#vYL8{op^=M|TkmhEa#(;|eAfcc<*o&*@ALd0NhaeR+ck}Y2NZ2!WH z^|U9!xAmKNq{?eE(jSt|>N!yl8VA1?$vN0@>%fGm@tp_n>uBXPcO&e{BVdMRZWGC% zB}Y{zlImy&Nu!>${|Xe6ojh*@^#6&h94f+W$knR(4df&hQX;87y<)x;pk>t;fEh!v zWH)6KO!t=l#(i=sHp!Lnb83M_uRihN5h8@V@s<&2sP1PU^-rFs#STXyhv>mCk;*ls zENbfVSZ7&%*6g6rxiZAN(a-mNNJmVDuGy^YVqr+xTnRp8=eS;c<5{a1@fF#pfR;Az z?_Uq#Gf>|Eilvc5wuml66i(}Dd|>(WhO4=|1g0N;rzdPiWynP$#_%uTJc_l9@@0DtAEl zy9X4T#VXMZGNdbLFHx|Ez|E86_6E}&}O?t92D#x@li zAdyDv70uVIH&+j}LRaukCW7dyxwN7hPMr}aqr@h**=FHd3)L4DTc)7e+B1mft@-XUTOm8C@!9f>FMd3 zTAsQZF0kQvNc-!%*D@F}f#U-NuH)8otE-zFA2qyFg$C_FX5%>37V)VLdk}+_D!laK zO?mJ~*O|s;%^I<}BmjYTpkBGtsBSTN>vG#@AV{y|FRxs_k&l^A&!uU)wgU{Mj0!t; zRQD$v0|lbFBz@_yXHZB#eq^XIR8JCh0q3PAw@O+9vumOXZ4UY7<{|;^tn1M5Xk=&j z?~6n`xoFi$ zjbDb$7{X&~Zc_)u7w?n?Jr@@>r%%bQtS8riQNr3mb;$T0j}xu6#;wh2G<$+GX*{!6 zU|rtGjj21!1IEE9d;_&e=rwv)|j1_BwdkT#K9iR#YQgpjfH&z-BoOQ&u=7=ck*v;nZ)g8zHU;GG^Vn ze^6VY6w%TfYwO#(g8p88mLFFu?8ORn^wji#K*ExZ2<&ua%97InHm{FVN(Ef#uJmIe zk*1?NTpiru43C!E%XGlzm64`PgwgM^TKseE!SQGzlj&xno@S1$aBT62+i!arYfz{_ zXLDuWcH5L1?TV--gF?AyG|DRu8mRkLz+uAgm7kI)4<*Hoq|x;k+6q);!l{(4`1m8u z8gr##cj)8jT~IK1h9vb~SOvk8eS4fqFXG^|O$6n8bpUVAgK)9E3H<1Yk%BCc+^O{~ z%y`)Ld{uB6K=r8#J^1LfM6v2Bt1I}!qC^~op>s)l;*mLw_{#J#(iuiF`IUT8I_K?o z0|>&ZMdZV-1q-jb0-LZx?)2If;;mYJEqZFY>VGP73!~kudGsA%e5~Wt^7Br;d6lK;@}gktnYfg z-keFr?fx*s3u{HPX#}%J{g+>?Ih(-3@)C3gJ~1|!2L0A7Ym7~tW=$53@+m1F!4%Cq zuCDpf)hVg{6vGPe9Qo_wG~;am zYYV_)z~Js>Xy$^Dp!DxOn?f*lQer)*&QWXk4+163Nor?72`S-qJL6p z4k%Sdg>5z&xx4d5C>ZeD*4!MrgZ3fy{{}3+eqFQmIZ>)e*W>-C{Yl&;4xYFhsI6!; zonb(@(9*A*r=TG0CrtOt^Q`{M@A|*5Yeb~#?tBG&A6#oMdg$gB013LO*}9V0K=)9~ zJe;~2j%IG)^fqtIy2g6TWh`t^Us-o^VmLnjckx_GQDLF#Si7;CB)btBFYhH?yF3~iI(UEDjJ9Om){WO3^kU?UXUsi8K2>FP^#y>XS6|?{y4H@+9h{vw zf!uCz?)tFvN5Eb~B$QBEhGp!}tTtRoEunu66rp)e+m`K3q0|h#TocOjm8bf0XHHJl zNx0>>_hC)h>N{76WQtlA6FqGz&pT4jB3la`!M;`d+D^yJNkXA3Rb>j``cM0oegJs? zf8Si&Vlgq$=NwP)U}W4qb`E=-MvOV~G)=bA0ghZCzYZ9;+q^^qtF%SAFoPl5kerEDpS8N3CW;nyT!d1;R+j}J@?%}AqpIwHVPy_CRZ|@w)UWdO z<7oaYF)du#60na4L9qgZ))5mLKknHpfPgfVVNifYGJo@PB z_pc>mYTRBrTAER9xE_I#J~FI}R4oe|AWo^EjBSW}`7W4PaxBLUdngAdMM+E@DgJ=^{F_qm_95+I>up|`g0R5DyT|~yLsybR;)a1D8rcYiJ zxc|)CksOnhETA>$@%H;FU3Y8{p6Sn(+%XNx<$yrP9CXd0ps(p5f}7JLdv5p&@E$PY zzvZ@odyW5=S^Pa^|6L-=WM#fiT^!cxJBaSbP_xY*%(MOm7;^b*6(0cc_=CjYpR7ow za2Ve#1&7<%H>r)DQB=Ta+sIVN4x@fU^(JX^mZDPG4#~GNHMiDZI4?4=-9sSU=$%#s zXvPc<2=PU`nBMZ(oxfK|nZG_VbEw%ZlUY|YHm4@#sMePYO$?(-?Cm0B$;NiNzFXZF zVt{4#?)(sL7U)stvM;HhEj;y}<>HwNClUzE4Wk>kAsehRm$dumF6u>sOb?>tP1S%s zYn1k2@65P{5upn^D_n>$5F@$*9qc_CA~fv}F8MNJzWDpX9TK-Q)0~6UK!=Z1-v`Gpl0J z0$P8i{q77U8%U|>YcTdt$OF(+Q1Igf9t(QkF&2f8Z3Pf>H7`LhTz-Fo=XBH;_(m%ekBz@kaCGQyv3 z1wXkVl;Q1bHN@R>xXLZ=k@USmOE)4C_gr4IJ)0x^TIql|oH_s;^}_d1KRk=&|J?-ej5j{qfU5kZ_N!>>sf4Pt zfoV$#-XuM?jqJLxqtUSmu<9ZKbDzml?be*kis0{Yv|p@PaT>ckLsE7FVt*h;)%5 zK56{69KWty+?P$2rK6{^;mB1fHC>BcLGwvZ|Mlae4B*r0*wlI6U-3VvDAMHluCFUh zdxkPpTXtzUSCZwWj`TG;()_1m+#fr$cRnq5gUaqW@H~?i8(hm;ANi*}1G;h&=58EJ zR2}_3JDGd8r?NrhsbaKWVH!w$QYl2{}TWHR4f(rEfXN^8ygy2J=}8hOoUy6 zvQzG|deA(Zo6%$` zqN$6F<4DfuAmofsbflziH4^z`IRUxei=>)y1)z;12`D5{QMFb**0)~)K&zm*82Qf? z4MW)?ZtcJXV6n{aN<4UanUb;o88cadd-byOa1lVU!E(I!!An5D{Q8)T=JsOmX1kFn zFI6TyDD3v2=mz?+MK&yK`pR{evU#90GIAVX^g-xP7+$-ZwgoCt(flJOh9V;0zt};> zR42D9%Rx=Z>@#uY|V zEer?lZf?}r{eb@ebnLU2m|ScwTg6tR3*>e#ZUywe0D7LSWy?qVn!Op@1j_^xTg!*| zK=tsrv;JL^kBO7a`)Td;6af-f`0E#kwQ5sT)Q0K`9k4P7buZQBQ);B}1wPGV3i3Qr zk?-)=967Hw!k2aPpEzNlz8aU8!)SduijgD*{OA;BZ_-fpR-+jJ=T_P`)>7RocT-&U zSZkp;O5NT*sI#9SgsXjDc+15(KjB?j8;3{mwy&SAIBgSf6v(Lk{EqfISzzz3zym&d z0=N}aR-XOS{_qi(&dX{SU`bwGLg=C-OE#$bK(UZ4FIzoMKNdoV0}kBut8*EZ`~;y} z8Q~HtYLuCBpNRq$-UOeE`b_{TWl8K26_Umbc^pk~9?cBDl&jk7W@-kxaNzi^*)X2- zxnLLS)ghc)LnE+WHrhg;lsmmRX;_cVlVLaBWaLY;Svmy<49JwANc^Q@tJZpf)2ZJz zF~KicA){&-6Bk!n(_<#}A!1GMLT+FG^eZ!ApdCKJ;of!N1#G5C2G&2($(f%i zDMJ-ADxN1$iA8#s5cf;6WXWuO@*#Cinp$rGR%w26bR#Nw#q&(V-R7!WueO ziDoKWWx4EzW{WootHr=XtUPs0RnQxEArj40X1C9u-&-YESi3<&3RX_1O)?h9G*g$o zR8+zwb85T`q>a(S#c0^aYD+US|JKc_^w@M>agfm1fFucJg}cc6%^`eFb_&1u{?9qX zqp=bxP}j3?#uma@y)y~Dy0pz%3oe|u!7BB>y753$WCqg&@yyg_bctJCM4=>vX8 zW?EK=C}-b)Y60TZ@4!{7NbumAKY-fDO&Trpr8Wx7b zMl{oGp0UxBjtwsM_J2+;^@mgyG(Mot*KIk$r35YnoSQOYn+^cv!T>31YeQO=%I7Dt zJX?#iSQDA$?=vm~aj~lKH;0uw&wT_9qEgsrqDc}%r~M7KY}~NkhZLBwRBq+-7bY1=9@?KM zVewD$-Pc{q@D+sSJJ`;Pq2!sAyN?VVEv6euY(iStjs<8qDAOQF6NDTy2?X-8Y?0LC6TX{YbVU^S(fBuoek!eHX(7?srDH<@^_RG86rzF_GX=alZI~3sI z$7g^>EY6~mAau(cYdGpB<~p3+I@rIFk!F8rp|+YVKxhIMromaRt4E#ntX+PC$~)Xi zHNW)}di1*dKl;q-)0Px5KXE55R>nF3MH%*?>HB4O9UY=kqJiU8bRaGa~JMP`|WR2X5tvbjIB<}#*v==7|gOdbn1 z-sQ`eGI?X~EV;n9)!kS&zYV!LW!jFe+zF>p<7(fZY1CBYYA@U|wV45+>Enn_ZgP|> zdlxF&0&%V+48j8!(ht=UIS5kys#bF@S5^iGbTdouGT+bjzu*zHlgJS!W=;IHmDFy? z2C2S{J&z+n4W@m7hksdj?ObU~>-D|bbO7<@{xsOUT-@|`tPS#Qtw7P3lyF`DbGW$Z zIg}`;V&%*ToxD}->n z7pM|ax5evcH~7ebVXW2dbT-Thg%;)j3x#~;h%c%1S^gMi-uMG4AZIB`NKhOR2s zz{pWzC^zsXQIf}5Y&9`-r9jT3QxO4Q{k+8wLC?19uo-uL1?;Z>6P_JHIkNw3^fPXC_*y>6B``Yf?QaOfED68N;L(r&ZGW9av2H5abV_Q3C{pWW) z_p40<>$JkOG7Lv=If`>thF6XAiwlkaeUa7oxzGrb17zsGz*z7r*9$;CbM76C7pDZD z7?h@tV64M2RIqe=e%s$P=toDqN}d7On{&X%? zGdKr71Kq-DOQ8FAcYrkQ{G(%w(w1N~$+yEud8!Wi&yX+VuYiUY+W*tvt`Y*oZSp8) zQzv7zO0kWzq|Uyvzbv)q-gpMVx-ExfPOxCnhHV0V=!i|^ej@JQQw#m{-=q2r4NlCE z`OKB0&H*hR2Mys*k-Yq|9eM39fll+Vsz<8NS3G+Dh;Y=bGt}# zC7BV($_`||m2z5Y${Yt+sJ<*Xp0l>Wis1ckEIY+4=$LUbMBKX>+!WIcrDOY-_*^1W zt8qk){uSpyXnXqPM#Lx#xnhT?q1ib~JY}GP z?!Jtc&{h6F2TjC<597AubH(w!vaGAehbN;cQGn#-#FgUTTwHhXJAgNngbTR))zzty zdKwOI&gQ>$#{qd;&KB0T>qotVKZfE2ZM;E@RRP9!AQCVo2!)3DQT+x7+oZRy! z+Zc(1#{OgZ)4SQI&(iC6j*!9sdw|XGtjZQPNzB`Z`sPMk0h?y5IEur@qgLIUW7eg^ zGU`HK%S`mmMU~Wa1?_GY_p;0kl|%KXVw(Y?X1uJ=u+S!Sa6>zB>H@AKHzja_*?vh2 zKSy8wfin%b(OjI>am)|IJWc0gP&-)*?g83RF;SM1*+F(B5VtwxPQybAKfeb|2q@5b)gP(>%3Lz=ZKO30d7V|X^nqxYn^MZ^ zxM;P<@o8}jb$VWq?{cP94MlWGw%eW}PKReGLx8Ef zreBBr;>_9Sx>O>r*!=UcX~P~;cTCbHpl`8RjN8K1GDnDGW7;1gPpvj=oLz(V4EEw1 zrZLmz=O&6R5?wR$A2LIQg33yNl_HzYAMx ze_lFHexK{NWP_Z$X91RXZI_M@39%noNO$7vVdl?ape>;1Pu-g{th{%!8oyN!(T!}` zy79F9GSVajXSLc;mkJ&4+{;pV23#o+5}E#erguZP<# zIs6>iL4goKHg&~nhclp89?BEf9seh6VDg}dOdA?4~ zwg9kH*;gLb2h5-1!mrLQaJltIxAG1?L5h?qfeT+FQ#3UO#yt)z5dpP=87)N`OVTJb zLBhGHSMebp#s~&=OtW;iC9x{?fzj^!Hc_AvvQ4+rO{dY|{!GGQDouM;BKBVozXh7M zH2{~#YBb`rqfR*Ta5~=KG!0MritN|X&V-C-SD$>#w_w>W*taCA)Vfub-wuQDH)yD3 zTYv>>`6(M+%aMv?vF_2edEEZUKv{UEe+zwo_fMcducTp#SQ-1|WIvLqzZz+7huj;D zkTFwGtOkMACtpC!|NE?ND_OloKXJeMk%8xrTu{;AJd{f?R}Yq?m_RvF>-m=2f6+&Y z+W-i=FL+(jCa} zLHU}$Y39olkp)wPI3?=bY!=EQhenEQTdVv7=^hfCptfo^bqvU)yXX4GEZHQ5^$ElA zR(A|iOaLGX=-qPQBO~QU@Bdz3R}7LckJPX3Rguj2O z^BndS5(^HN%2t3ciDHgdB7U$qsra3#kGz4;>ApDA5*)g-HrG)DXlxq<1QfZ@%{BW_#rElcsdbdiv_vDlHmfrWC5vN6L& z%-Y7fx=vG8%Z2g~xc&he(2d=j2nw#U91QNKMi4(2+yrpp+}uN-&DoRW(C`Eb569PZ$dmX7L@h{y6_Ri$K3VC81? zAK8_MYnBK2gXPB7{OsAKDY^?wcTr=^hh{aCg=!Q2WJFi>Eb1K!_$$WBcM|| z87WsU>-G;ZnAc%}V*39B1Ji1Hjg+S;t=cd9A;ph`Zfs1PVl3k8KRb3OQ$|$N$O;gMD;w|F~X6EiNsoX}gU! zY*AUuA7tnOj6Q;Vbljk^XstZS_s12nv`}oW*@kGUE|rk~)B=h^DnC<>@4cCp0S?%@ zhnxvhDZEKBDT+_+>N`ZEWg|6p+9ZJ}5|VryTYI3vj)g`bGM5TM^WtpV-gzp*@H{1p z{(fp{V?}3czO=jJSk;0lq}EM*WOfe5_e^_ljO6FDK{f+mCZF_)y+$U6b0^wgr^rfm zAR_~VMU#)_PAfV~kShxaKL^Am^>JEDOUo2-qKC<|SC^X`hrl~?B;Zk}xbo)iMxmm_ zhw0=FWgJ|QlBcM0@36T0uIE6_5x?dN2FYp#>F}jS&a^n0KBO&S@~mZ! zo9K$|k5nk=XQ0dmZ8a1G*kWz)6eKvXJb0&CpwdSg`&di$ntk{n9(oPme!a%iZWrS)qWD0!RE?Nd2tZ_5|-mvjD7?*zU_`3^g!* zRfoLLRRBU5%Gy$7B7|u~IMbMTEj1r!$e^OePlG|$8r26fN|Fw4rn*=N>R0uplf<*R zdM)JePd;(O8MVUKt@WS-SxWLk7j)m&lF90jJ)3xTyVIxK>gvZsK{u-}yIh<2UwERz z+pq1S`1GZs79^E(i<#~jI9=Vbzq}wB{R^nG?qD!SG1DnN{ECn*n!$VXCNXHdOti>C zf8atm#;)bfR_xTVvUpR!E}>Ek9ME7#4`gGV)*YY1Fyy+}SE#yyI)Vn4?~tHweA z4G8}NhA@1FElR*iM#kD(URsk^BnseIH9tddpIkdLn=LGYrTha4H#>82sB_QMZ@Q74 zvm7n@@iHWhx~=E=P>S_J_m_6kQr4EeTVQzVB;>-&o$a_npf;GTaVv)U0+=T?2+!LS z6l=mL00()X%#XzFFv?foSf41ftqiF#G~D`2?uCK^a8v@wLZe6CX)tI;SDl{$gKoZM z!j@$nK+JmnDgbHz_S}}6@)bkIqg16nI}53>KfNU4vbLcBNGKp$W9wx1A7D7tsNQ=h zp*s_XD5tR3Ie18*wu})Iy|uZW#uEcT&s1;#lLoL`_4ORw9P?70H7@`1zoD$~VHm5# zj4PF>o7d4nQ##}be|VoBBI#C_S<%q+J2E&VSn4NSmcKuUnxFgo{{GFN#m^h9ZKQ0= zSvf};$%Q;R*YcGLErz?c?3b3b_Eh*q0r;aI18B9+Ylujw-_-x4b?*f| zuQ-y*e`Y0em=m^)P3rq>MNh~$@Jl5NjGs>$X#c`6wza;1@vRnKw9OtfRX~l=(7Oq5 zC~RPB{S{9*AyYKrR;PJdDGabeWLU={7aeux&vhnXH0%rY#ukZ(aHf46bN=yELNrT0 zN1qjl40}HfhF@tEYVg)nEC_{uDdp1t~?LaNM z0^BBH!jOW5MRJ|g+|=@Nn6+w>VA0f6mmb|i+R+~H(}|9bop5y*hF32dv#-H^*gqtRoH)BEd z+QQMX3}nb#xk?2zMdnpbS@dC((t4JvG?sOzCOCx3FyiUJGsC#jIlV><4-rx z?m!i>{@rQ149KwKFNf8aTKHhU8fP9z))uKoJQ=a#Le72xS7*G*iBR)FP?~l!aGYkE zme8VCrH=UHs^$CjOI!%uh+$BFtf05HEGELWQxxsQbu7Ra{hfPNigZZNI8jG?_fP9M zxJQH&WNci?9cG7_XacW5Zw~|nONkt_mOTJX0_O5 z<;?0cD+=l1&gj8iyCD(!K9g_{N*!4Rawy?v;kXDpao!7b^F-d zIMIf{|=9tj_d*(~01Wh-L^UO1KC@WVRhjhSyDbOqE& z@69uhrB@CcGXA;otV~dznJm_6Ro0am^L*^`ppgpRKHEL%k~k-7A(PFb_?I{E5`1gY z#(JozL0T$P#9I6LKl|chAe;*dX|*}}nM?clSA^9aH(fF+u5sYpPDm@|07oFit`0w^ zLM9pZ*;ar7V%jZwEssbMaNn=V|QpJlbWc$-9Pwcdb*`F zyfZJ2Q@1ikBa_W!srfYf$D52*p8+V(M>I~*vOBEDYtZPDlkIN+xUHAeeR}9^9rA)sQA*+A(f*djbYwtg=^55 zWs3-|BrLM9IfX8Cq+Y8uzZB8Jmri8dbznN zr|B>O%|F(IjYGR@*G#8KwZ$e2U*L(=z`;4%_FrMuscfeOlmkvj^U3s7V49`C>h-^r zRjaD%9v!x(A9*u#Lvh60UHfiRfNn>M$A$I?Zv8qS;w9rB0CXXDf}{0i^jdU7oYicz z+05EPH;Ouu3s?x<)>8|nKK6G(oVmE?oe=)aH9^E{=|6xABCo;cxIXx|@=r|pjWSvi zjLN`PF9v(wNj;h0sVm)HNNQM;Rz8&?+~$=<}#Z*vRU>(W?0qgOwCEh zUDmcdtYD>C<1Qsrk}OKEQ(quvspP1{@uWepa9?NI?9|pVWw!pKh1C{_1OQD}9<3C! zI-9Frf;%)ys0o(BNe$!;g@mhwJCpHjhq;$kr;of1Kz;4km%pU)n^f(N(o5k74^hS2 z63wT%jE9E$Mvk#RRpqet`1FUnsYAUU7eS4t0n9<9Ts)LpTF5VT-4zNyg?#h#Z3eDA zrpLQ=LBG0|)Ms)GvX1mIZuQ;Nzwqm1jZsHaBX3YDQ$uDO!Wy1JC!A8Oz8r8KtRz;h zu2fmNMND%Hl@a}N9tv&ge4Y%rGY0@uyr){aRH$I#?hF|RVn*{f2mXO_pn_T8`iWal z=+z|x3KE|i*Vb$wAR+IV$l$G9n>kH8d}Tl2P|_}XjZ_gGVK(z89AjbM=`}EV zI+(Y*!=hgy#)NMa3%vM&Togl7;yc|BR7ORY)w;q*HZ)$DV^CA{Ach)H9gTY_hx%Re zuiV|1-6mxH_TTj$@AYm(CI{3~Tovvt?HwtKc6J2=OU)6MXIU< zmGcopq=17)Y5njAuSV|+NMYtU0z!u~^Io<*Y<~WNE!->axux3B9N$I1@s&86ldBWk zUvcurrD+)rD%MoE4jff?rpyU-T2hNS%^d@21MRXJ+F9pUHE(F9aFM%o3*tw1nlC%G z{hrwPvWXd&)Rs8=o&n{15p!>>-{I4XWlb}K7D1RwmVqvfl&X$XybQom>-em#D^DMF z7N4lQoKz^Ir$S&I9*jzh9W=_swsv*p3@A|$1AgCr)4CGZrDN8lP+zdD-AuLum;P%~ zCg2yEvf2$M17A}LISL~F7)xec5K_wv##8fya-RkJ!KVD}7IWg-b)R9@*le^D*ZhJL zJj$X6=?PE-^5_yi;EP|~I+D(CxtMm1_{?f2yn>N*S=Y#c1X+o#+Kf-1`u9d{o!YBDruhCz4o!Q)00IS>hFG{o5vHELuy>7xiSCXt-D^({43h4)j> ze80IH@bsJ?y7jmS`&W}~?ZIPoQn0yEL2b#2(f4%!2Q+VSXxh#aoAbi50bmh;mUg|x zzPRKVtNrb)-~ii!ELRd|@fUWnJDPLRg-VNf(V{!2z1-9OM4+F>J;QCmZtuCc5RK7Jo%q68%!!x;O zDo~&VkcE?>qZSZyani`~3VSI=O!%(YX!?q7W63UIEp{D4y##8^kg+s2Fbs>Y@$0pg zieE|&P)}RWbN>1g4wORw$NNmlWvPlc)ma&k$_kYVft&aFpXSQp>{M=BQoY-3`6KJ0 z7Gg>*hG(^<#ipgX%M9QQblo>$AeOOlG!5d;;*}&O!w5x(arqcj3mxlr^@kS&`!ou`$uVZF0dDzzW=8H|uoVHq4R?t}fNR7XT zWgXlQasq^l*k8pwVz()4)y-EnFMZs})-9G-B%RLRd%~4cP$`%KtsUG@3V~q1|I`AE z-mIOvA_OQ<=LplbxH8lrPms7vzcML#5CkIzHCeg!$8G1wKW$KHLSk|WL&b%;&Ii4M z)2XHQrwCUBT!JKKSerFe23ipMk1xPP)0)md=smzo0QI~4kGL3Q8zEPq5rDkb;Rra5jAXpw{nw$0h8CDjKKi}#Sjty*QAH;*(9lX_*emV^vDRsD z+=n_Lvgr-_qawEfNyCQBh~*?64)FoRTJo6l2xi(syELgkg_PGd>YH*^$~Wr0f4=KlUq77uXy%qOso`M++v0O^w>msaRy?&@xpJKX5D1gU-l zP^f0d2PG|YN=zDbqpg^rmu48s%J|(G*azzen|nvwhg*6JWVzso(hvW-wf#!>l-l~z z5b)xaMo+%dKA`!(O8D0c+mKHS^|x}0Lj1e4WLq_A}-wm78E5MG!!hr!YyZ&#*}OJhn( zP{>N*@g^O&f&3%h>7Mf815=kND*=BDkKQ?B_7_v1MUY4W9Cg1TbBd&79}R7BNsI$_ zUVb)D+X@9SZ8R7*fL?_N2zam<-J8y%0ar~DxGTs!r=Oq&4vRjJX=M!TfV+TOK$sC!qHhhha^6eNg)$67 zHqDwQBau=3s--&o($8OuSeM*)D<|;I$tx2cov~SQtk~yuh(E}}ng#v~lpfVVw9Fne zOG!Bh@Z{^DH**-JFOV#eFLGp~U@8&Z*?kW(OOEqVo~kWnT)-tlUzKF5#ZD1qhzia| zW%9O4?6CxDsP+LGKi`0vZu<@fpg~E|e+0Zn9qL(|l^~N0NMyo;{q?mc>IYq8{(ma1YLkX42IZs){gG_x}*E1l@?B5n(ASP0^-!)rfDKPGcN}~=(msA zddkQg&R=K(ilM1%-|3WS*2$@n>R;`ivKl3{k1eN{zBM-)S$UxLix-w1o)w_!EqCwh z_nUZ{bqga#mFM#0L9TUbvfnJdt}goHmklWb4#)G|@+gk≈(6FY-YqE5bm$%cC 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 U@)#JoL046|t9hsRw&k<`0kY^HvH$=8 diff --git a/backend/colors-v2.png b/backend/colors-v2.png deleted file mode 100644 index d6c56764abc9c68ec1c278ab4c035220096fd72e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74815 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 diff --git a/backend/migrations/20260423050000_script_trigger.up.sql b/backend/migrations/20260423050000_script_trigger.up.sql index 9abc42b1f1..cb105ba3f3 100644 --- a/backend/migrations/20260423050000_script_trigger.up.sql +++ b/backend/migrations/20260423050000_script_trigger.up.sql @@ -6,9 +6,18 @@ -- `// on ` lines (every non-integration trigger kind; their -- trigger_ref is the trigger row path, or empty for marker-only forms). -- +-- Per-edge columns that are in fact script-level properties (every row for +-- a given runnable carries the same value, set once at deploy) but live on +-- the edge so the dispatcher reads everything from a single query: +-- join_all `// trigger all` AND-join barrier (else OR, the default). +-- debounce_s `// on debounce=` (else script-level `// debounce`); only +-- asset-cascade edges carry one. NULL = no debounce. +-- retry_* `// retry []` cascade retry. NULL = none. +-- -- The idempotency guards (IF NOT EXISTS / duplicate_object) are load-bearing: -- this migration squashes several pre-release ones, so databases migrated --- from the unsquashed history already contain the final objects. +-- from the unsquashed history already contain the final objects and +-- re-applying must be a no-op. DO $$ BEGIN CREATE TYPE SCRIPT_TRIGGER_KIND AS ENUM ( 'asset', 'schedule', 'webhook', 'email', 'kafka', 'mqtt', 'nats', @@ -22,7 +31,11 @@ CREATE TABLE IF NOT EXISTS script_trigger ( runnable_kind ASSET_USAGE_KIND NOT NULL, runnable_path VARCHAR(255) NOT NULL, trigger_kind SCRIPT_TRIGGER_KIND NOT NULL, - trigger_ref TEXT NOT NULL + trigger_ref TEXT NOT NULL, + join_all BOOLEAN NOT NULL DEFAULT FALSE, + debounce_s INTEGER, + retry_count SMALLINT, + retry_delay_s INTEGER ); -- Per-runnable lookup (wipe-on-deploy, list-triggers-for-script). diff --git a/backend/migrations/20260516190545_add_script_trigger_join_all.down.sql b/backend/migrations/20260516190545_add_script_trigger_join_all.down.sql deleted file mode 100644 index b69f7abca6..0000000000 --- a/backend/migrations/20260516190545_add_script_trigger_join_all.down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE script_trigger - DROP COLUMN join_all; diff --git a/backend/migrations/20260516190545_add_script_trigger_join_all.up.sql b/backend/migrations/20260516190545_add_script_trigger_join_all.up.sql deleted file mode 100644 index b3aa5b31df..0000000000 --- a/backend/migrations/20260516190545_add_script_trigger_join_all.up.sql +++ /dev/null @@ -1,9 +0,0 @@ --- AND-join barrier (`// trigger all`). When true, the subscriber runs only --- once every partition-bearing input has materialized at the same partition --- (plus every reference input exists), rather than firing on any input (OR, --- the default). Stored per script_trigger row — it is a script-level --- property so every row for a given (workspace, runnable) carries the same --- value; this matches the wipe-and-reinsert-on-deploy pattern and keeps the --- subscriber lookup a single query. -ALTER TABLE script_trigger - ADD COLUMN join_all BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/backend/migrations/20260516201317_script_trigger_debounce_s.down.sql b/backend/migrations/20260516201317_script_trigger_debounce_s.down.sql deleted file mode 100644 index fb0950f26f..0000000000 --- a/backend/migrations/20260516201317_script_trigger_debounce_s.down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE script_trigger - DROP COLUMN debounce_s; diff --git a/backend/migrations/20260516201317_script_trigger_debounce_s.up.sql b/backend/migrations/20260516201317_script_trigger_debounce_s.up.sql deleted file mode 100644 index 7ef44f22c6..0000000000 --- a/backend/migrations/20260516201317_script_trigger_debounce_s.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Opt-in debounce window (seconds) for an asset-cascade subscriber edge. --- NULL = no debounce (fan-out, the default/current behaviour). Resolved --- per `// on` edge at deploy as: edge `debounce=` ?? script-level --- `// debounce ` ?? none. Only asset subscriber rows carry it; the --- dispatcher builds DebouncingSettings keyed (subscriber, partition) so --- distinct partitions never collapse and "latest in window" falls out. -ALTER TABLE script_trigger - ADD COLUMN debounce_s INTEGER; diff --git a/backend/migrations/20260523054912_script_trigger_retry.down.sql b/backend/migrations/20260523054912_script_trigger_retry.down.sql deleted file mode 100644 index 49ec6bd823..0000000000 --- a/backend/migrations/20260523054912_script_trigger_retry.down.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE script_trigger - DROP COLUMN retry_count, - DROP COLUMN retry_delay_s; diff --git a/backend/migrations/20260523054912_script_trigger_retry.up.sql b/backend/migrations/20260523054912_script_trigger_retry.up.sql deleted file mode 100644 index 8eb9adca96..0000000000 --- a/backend/migrations/20260523054912_script_trigger_retry.up.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Cascade retry policy (`// retry []`). Stored per --- `script_trigger` row — like `join_all`, it is a script-level property --- (every row for a given runnable carries the same value) but lives --- alongside the edge so the dispatcher reads everything it needs from a --- single query. NULL = no retry (current behaviour); set = re-run on --- failure up to `retry_count` times, waiting `retry_delay_s` seconds --- between attempts (0 = back-to-back). -ALTER TABLE script_trigger - ADD COLUMN retry_count SMALLINT, - ADD COLUMN retry_delay_s INTEGER; diff --git a/backend/migrations/20260523055641_dispatch_event.up.sql b/backend/migrations/20260523055641_dispatch_event.up.sql index f36c1dab45..51403f33ce 100644 --- a/backend/migrations/20260523055641_dispatch_event.up.sql +++ b/backend/migrations/20260523055641_dispatch_event.up.sql @@ -9,13 +9,19 @@ -- retention sweep (monitor.rs delete_expired_jobs_batch -> DELETE FROM -- v2_job WHERE id = ANY(...)) reaps these rows along with their producer. -- No separate cleanup path needed. -CREATE TYPE DISPATCH_OUTCOME AS ENUM ( - 'dispatched', - 'join_pending', - 'skipped' -); +-- +-- Idempotency guards (duplicate_object / IF NOT EXISTS) are load-bearing: +-- re-applying this migration after a squash must be a no-op. +DO $$ BEGIN + CREATE TYPE DISPATCH_OUTCOME AS ENUM ( + 'dispatched', + 'join_pending', + 'skipped' + ); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; -CREATE TABLE dispatch_event ( +CREATE TABLE IF NOT EXISTS dispatch_event ( id BIGSERIAL PRIMARY KEY, workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id) ON DELETE CASCADE ON UPDATE CASCADE, producer_job_id UUID NOT NULL REFERENCES v2_job(id) ON DELETE CASCADE, @@ -41,5 +47,17 @@ CREATE TABLE dispatch_event ( -- Primary access pattern: list events for one producer (the job detail -- panel). Ordered scans by id give chronological order for free. -CREATE INDEX idx_dispatch_event_producer +CREATE INDEX IF NOT EXISTS idx_dispatch_event_producer ON dispatch_event (producer_job_id, id); + +-- Backs the asset-graph edge listing (jobs.rs list_asset_dispatch_edges): +-- WHERE workspace_id = $1 AND subscriber_path LIKE 'prefix%' +-- AND created_at >= $3 +-- ORDER BY created_at DESC, id DESC +-- The (producer_job_id, id) index above doesn't help this access path, so +-- without this one a high-volume dispatch_event seq-scans + sorts. +-- text_pattern_ops makes the anchored LIKE prefix (built as `path_start || '%'`) +-- index-usable regardless of the column collation; created_at DESC matches +-- the ORDER BY so Postgres can satisfy ordering from the index. +CREATE INDEX IF NOT EXISTS idx_dispatch_event_subscriber + ON dispatch_event (workspace_id, subscriber_path text_pattern_ops, created_at DESC); diff --git a/backend/migrations/20260523091727_backfill_asset_from_script_assets.down.sql b/backend/migrations/20260523091727_backfill_asset_from_script_assets.down.sql deleted file mode 100644 index 5a60bddcdf..0000000000 --- a/backend/migrations/20260523091727_backfill_asset_from_script_assets.down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- No-op: the backfill is a one-shot data migration. We can't safely --- distinguish backfilled rows from those a subsequent deploy inserted via --- the normal `insert_static_asset_usage` path (same tuple, no marker), so --- rolling back would risk deleting live data. Leaving the rows in place on --- downgrade is harmless — they'll get rewritten on the next deploy of --- each affected script. diff --git a/backend/migrations/20260523091727_backfill_asset_from_script_assets.up.sql b/backend/migrations/20260523091727_backfill_asset_from_script_assets.up.sql deleted file mode 100644 index ecc77fcf0b..0000000000 --- a/backend/migrations/20260523091727_backfill_asset_from_script_assets.up.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Backfill the `asset` table from `script.assets` JSONB for scripts that --- were deployed before the deploy path started populating asset rows from --- the parsed `ns.assets`. The JSONB column carried the parser's findings --- the whole time; the corresponding asset rows are what `fetch_producer_writes` --- (asset-trigger cascade) and the asset-graph lineage view actually query. --- Without this, a pre-feature script "succeeds" but the dispatcher sees --- no writes, no subscribers are matched, and the dispatch_event panel --- stays empty. --- --- Idempotent: scoped to (workspace, path) pairs that have ZERO asset rows --- under `usage_kind = 'script'`, so re-running can't duplicate. The --- (workspace_id, path, kind, usage_path, usage_kind) primary key catches --- any residual overlap via `ON CONFLICT DO NOTHING` as a belt-and-braces. --- Skips archived/deleted script versions so we backfill from the *current* --- snapshot, matching what the live deploy path would write. -INSERT INTO asset (workspace_id, path, kind, usage_access_type, usage_path, usage_kind) -SELECT - s.workspace_id, - a->>'path', - (a->>'kind')::asset_kind, - (a->>'access_type')::asset_access_type, - s.path, - 'script'::asset_usage_kind -FROM script s -CROSS JOIN LATERAL jsonb_array_elements(s.assets) AS a -WHERE s.archived = false - AND s.deleted = false - AND s.assets IS NOT NULL - AND jsonb_typeof(s.assets) = 'array' - AND a->>'path' IS NOT NULL - AND a->>'kind' IS NOT NULL - AND NOT EXISTS ( - SELECT 1 - FROM asset existing - WHERE existing.workspace_id = s.workspace_id - AND existing.usage_kind = 'script' - AND existing.usage_path = s.path - ) -ON CONFLICT DO NOTHING; diff --git a/backend/parsers/windmill-parser/src/asset_parser.rs b/backend/parsers/windmill-parser/src/asset_parser.rs index 1ac1638959..31b9815698 100644 --- a/backend/parsers/windmill-parser/src/asset_parser.rs +++ b/backend/parsers/windmill-parser/src/asset_parser.rs @@ -281,13 +281,12 @@ pub fn merge_assets(assets: Vec) -> Vec { .iter_mut() .find(|x| x.path == asset.path && x.kind == asset.kind) { - // merge access types + // merge access types — a None on either side means ambiguous + // usage (unknown access), which poisons the merge to None; + // otherwise delegate to the shared truth table. existing.access_type = match (asset.access_type, existing.access_type) { (None, _) | (_, None) => None, - (Some(R), Some(W)) | (Some(W), Some(R)) => Some(RW), - (Some(RW), _) | (_, Some(RW)) => Some(RW), - (Some(R), Some(R)) => Some(R), - (Some(W), Some(W)) => Some(W), + (Some(a), Some(b)) => Some(merge_access_types(a, b)), }; // merge columns: union the column sets and merge access types per column existing.columns = merge_column_maps(existing.columns.take(), asset.columns); @@ -391,10 +390,11 @@ fn unquote(s: &str) -> Option<&str> { // `ident=` token — the same assumption `// partitioned` already makes. fn split_trailing_kv_opts(s: &str) -> (&str, BTreeMap) { let mut split_at: Option = None; - let mut pos = 0usize; for tok in s.split_whitespace() { - let tok_start = s[pos..].find(tok).map(|o| pos + o).unwrap_or(pos); - pos = tok_start + tok.len(); + // `split_whitespace` yields slices borrowed from `s`, so the exact + // byte offset is the pointer delta — substring search (`find`) would + // misfire when an earlier token also appears inside a later one. + let tok_start = tok.as_ptr() as usize - s.as_ptr() as usize; if let Some(eq) = tok.find('=') { let key = &tok[..eq]; if !key.is_empty() @@ -489,6 +489,20 @@ fn parse_kv_opts(s: &str) -> BTreeMap { // multiple lines declare them, the first one is kept (last would be // reasonable too, but first matches the file-top convention developers // follow). +// Try to consume `` as a complete word from `rest`. Returns the trailing +// text after the keyword if it matched (empty or whitespace-bounded), +// `None` otherwise. Prevents `partitioned` matching `partition`, `pipelines` +// matching `pipeline`, etc. Mirrors `consumeKeyword` in +// parsePipelineAnnotations.ts. +fn consume_keyword<'a>(rest: &'a str, kw: &str) -> Option<&'a str> { + let after = rest.strip_prefix(kw)?; + if after.is_empty() || after.starts_with(|c: char| c.is_whitespace()) { + Some(after) + } else { + None + } +} + pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { let mut out = PipelineAnnotations::default(); @@ -505,7 +519,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { }; let rest = rest.trim_start(); - if let Some(after_kw) = rest.strip_prefix("pipeline") { + if let Some(after_kw) = consume_keyword(rest, "pipeline") { // Strict: keyword must be the only content on the line. Rejects // `pipeline broken`, `pipelines`, `pipeline-related`, etc. if after_kw.trim().is_empty() { @@ -514,10 +528,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("partitioned") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "partitioned") { if out.partition.is_none() { if let Some(spec) = parse_partitioned_spec(after_kw.trim()) { out.partition = Some(spec); @@ -526,10 +537,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("freshness") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "freshness") { let dur = after_kw.trim(); if !dur.is_empty() && out.freshness.is_none() { out.freshness = Some(FreshnessSpec { duration: dur.to_string() }); @@ -537,10 +545,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("trigger") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "trigger") { match after_kw.trim() { "all" => out.join_mode = JoinMode::All, "any" => out.join_mode = JoinMode::Any, @@ -550,10 +555,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("debounce") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "debounce") { let dur = after_kw.trim(); if !dur.is_empty() && out.debounce_default.is_none() { out.debounce_default = Some(dur.to_string()); @@ -561,10 +563,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("tag") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "tag") { let name = after_kw.trim(); if !name.is_empty() && out.tag.is_none() { out.tag = Some(name.to_string()); @@ -572,10 +571,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("retry") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "retry") { if out.retry.is_none() { if let Some(spec) = parse_retry_spec(after_kw.trim()) { out.retry = Some(spec); @@ -584,10 +580,7 @@ pub fn parse_pipeline_annotations(code: &str) -> PipelineAnnotations { continue; } - if let Some(after_kw) = rest.strip_prefix("on") { - if !after_kw.starts_with(|c: char| c.is_whitespace()) { - continue; - } + if let Some(after_kw) = consume_keyword(rest, "on") { let spec_text = after_kw.trim(); if spec_text.is_empty() { continue; @@ -1082,6 +1075,33 @@ mod pipeline_annotation_tests { assert_eq!(r.delay.as_deref(), Some("5s")); } + #[test] + fn split_trailing_kv_opts_uses_exact_token_offset() { + // Regression for the token-offset computation in + // `split_trailing_kv_opts`. The asset ref's path token contains the + // exact text of the trailing `debounce=60s` opt as a substring, and + // the same `debounce=60s` text also appears a second time before the + // real opt. Offsetting by the `&str` slice's pointer is exact; a + // substring scan (`find`) is what this guards against regressing to. + let (ref_part, opts) = split_trailing_kv_opts("s3://lake/debounce=60s/raw debounce=60s"); + assert_eq!(ref_part, "s3://lake/debounce=60s/raw"); + assert_eq!(opts.get("debounce").map(String::as_str), Some("60s")); + + // End-to-end through the annotation parser: the ref must parse to the + // full S3 path (not truncated at the embedded `debounce=`), and the + // per-edge debounce override must be picked up from the trailing opt. + let out = parse_pipeline_annotations("// on s3://lake/debounce=60s/raw debounce=90s"); + assert_eq!(out.triggers.len(), 1); + match &out.triggers[0] { + TriggerSpec::Asset { asset_kind, path, debounce } => { + assert_eq!(*asset_kind, AssetKind::S3Object); + assert_eq!(path, "lake/debounce=60s/raw"); + assert_eq!(debounce.as_deref(), Some("90s")); + } + other => panic!("expected asset trigger, got {other:?}"), + } + } + #[test] fn kv_opts_quoted_with_spaces() { let m = parse_kv_opts("a=\"hello world\" b=plain c='single quoted'"); diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index c8ed841e19..7b8d468da8 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -282,6 +282,12 @@ pub async fn migrate( 20260207000002, 20260207000003, 20260207000004, + // Squashed pre-release pipeline migrations: the per-column ALTERs on + // script_trigger and the dispatch_event subscriber index were folded + // back into these two CREATEs, changing their checksum. Both are + // idempotent, so re-applying on an already-migrated DB is a no-op. + 20260423050000, + 20260523055641, ]; for m in migrator.migrations.iter() { if m.migration_type.is_down_migration() { diff --git a/backend/windmill-common/src/assets.rs b/backend/windmill-common/src/assets.rs index 7fac7d2c68..ebdb5bfb54 100644 --- a/backend/windmill-common/src/assets.rs +++ b/backend/windmill-common/src/assets.rs @@ -211,13 +211,10 @@ pub fn parse_asset_trigger_ref(s: &str) -> Option<(AssetKind, String)> { pub fn trigger_spec_to_row(spec: &TriggerSpec) -> Option<(ScriptTriggerKind, String)> { match spec { TriggerSpec::Asset { asset_kind, path, .. } => { - let prefix = match asset_kind { - windmill_parser::asset_parser::AssetKind::S3Object => "s3://", - windmill_parser::asset_parser::AssetKind::Resource => "$res:", - windmill_parser::asset_parser::AssetKind::Ducklake => "ducklake://", - windmill_parser::asset_parser::AssetKind::DataTable => "datatable://", - windmill_parser::asset_parser::AssetKind::Volume => "volume://", - }; + // Single source of truth for the canonical prefix lives on the + // common AssetKind; map the parser kind across first. The parser + // enum has no Variable variant, so canonical_prefix is always Some. + let prefix = asset_kind_from_parser(*asset_kind).canonical_prefix()?; Some((ScriptTriggerKind::Asset, format!("{}{}", prefix, path))) } // Schedule joins the native-trigger family — no script_trigger row diff --git a/backend/windmill-common/src/partition.rs b/backend/windmill-common/src/partition.rs index 14bfbdc173..2028438909 100644 --- a/backend/windmill-common/src/partition.rs +++ b/backend/windmill-common/src/partition.rs @@ -14,8 +14,14 @@ //! - `dynamic key=`: extracted from the triggering payload via a //! minimal `$.a.b` JSON path (the realistic per-tenant/shard/event case). +use std::collections::HashMap; + use chrono::{DateTime, NaiveDate, Utc}; use chrono_tz::Tz; +use serde_json::value::RawValue; +use sqlx::types::Json; +use sqlx::PgExecutor; +use uuid::Uuid; use windmill_parser::asset_parser::{PartitionKind, PartitionSpec}; use crate::error::{Error, Result}; @@ -24,6 +30,60 @@ use crate::error::{Error, Result}; /// mirrored at `trigger.partition` for cascade propagation). pub const PARTITION_ARG: &str = "partition"; +/// Persist a freshly resolved `partition` into a job's `v2_job.args`, +/// merging it on top of whatever args already exist. +/// +/// INVARIANT: partition, once resolved, is immutable for the job's +/// lifetime. This is the single place that *establishes* it (run-start +/// resolution); every later args rewrite must preserve it — see +/// [`merge_args_preserving_partition`]. Keeping both the set and the +/// preserve here keeps the JSONB shape and the invariant in one module. +pub async fn set_resolved_partition<'e>( + executor: impl PgExecutor<'e>, + job_id: Uuid, + value: &str, +) -> Result<()> { + sqlx::query!( + "UPDATE v2_job SET args = coalesce(args, '{}'::jsonb) \ + || jsonb_build_object('partition', $1::text) WHERE id = $2", + value, + job_id, + ) + .execute(executor) + .await?; + Ok(()) +} + +/// Replace a job's `v2_job.args` with `new_args`, but carry forward any +/// existing top-level `partition` key so a wholesale rewrite (e.g. the +/// preprocessor swapping in preprocessed args) cannot clobber a resolved +/// partition. +/// +/// INVARIANT: partition, once resolved, is immutable for the job's +/// lifetime (set by [`set_resolved_partition`]); this guards every later +/// args rewrite against dropping it. +pub async fn merge_args_preserving_partition<'e>( + executor: impl PgExecutor<'e>, + job_id: Uuid, + new_args: HashMap>, +) -> Result<()> { + sqlx::query!( + "UPDATE v2_job + SET args = CASE + WHEN args ? 'partition' + THEN $1 || jsonb_build_object('partition', args -> 'partition') + ELSE $1 + END, + preprocessed = TRUE + WHERE id = $2", + Json(new_args) as Json>>, + job_id, + ) + .execute(executor) + .await?; + Ok(()) +} + fn default_format(kind: &PartitionKind) -> &'static str { match kind { PartitionKind::Hourly => "%Y-%m-%dT%H", @@ -40,7 +100,7 @@ fn default_format(kind: &PartitionKind) -> &'static str { /// `spec.start` (anchor: older partitions are not backfilled). Errors on an /// invalid `tz` or `start` rather than silently materializing the wrong /// partition. `Dynamic` is rejected — use [`extract_dynamic_partition`]. -pub fn resolve_time_partition(spec: &PartitionSpec, at: DateTime) -> Result> { +fn resolve_time_partition(spec: &PartitionSpec, at: DateTime) -> Result> { if matches!(spec.kind, PartitionKind::Dynamic { .. }) { return Err(Error::BadRequest( "resolve_time_partition called on a dynamic partition".to_string(), @@ -74,7 +134,7 @@ pub fn resolve_time_partition(spec: &PartitionSpec, at: DateTime) -> Result /// `$.a.b.c` dotted path (no brackets/wildcards/filters — that covers the /// realistic per-tenant/shard/event keys). The leaf must be a string, /// number or bool. -pub fn extract_dynamic_partition(key: &str, payload: &serde_json::Value) -> Result { +fn extract_dynamic_partition(key: &str, payload: &serde_json::Value) -> Result { let path = key.strip_prefix("$").unwrap_or(key); let path = path.strip_prefix('.').unwrap_or(path); let mut cur = payload; diff --git a/backend/windmill-queue/src/asset_dispatch.rs b/backend/windmill-queue/src/asset_dispatch.rs index 6bb58373ea..49e278fdda 100644 --- a/backend/windmill-queue/src/asset_dispatch.rs +++ b/backend/windmill-queue/src/asset_dispatch.rs @@ -94,8 +94,8 @@ enum DispatchOutcome { Skipped, } -/// Outcome-specific fields. `record_event` takes the four "always-present" -/// columns positionally and bundles the rest here so each call site only +/// Outcome-specific fields. Event constructors take the four "always-present" +/// columns positionally and bundle the rest here so each call site only /// names what it actually carries. #[derive(Debug, Default)] struct EventOptions<'a> { @@ -107,6 +107,46 @@ struct EventOptions<'a> { reason: Option<&'a str>, } +/// One accumulated `dispatch_event` row. Owned (not borrowed) so the whole +/// dispatch pass can collect rows and flush them in a single batched INSERT +/// at the end, avoiding an N+1 (one INSERT per subscriber × asset write). +#[derive(Debug)] +struct EventRow { + subscriber_path: String, + asset_kind: AssetKind, + asset_path: String, + outcome: DispatchOutcome, + child_job_id: Option, + partition: Option, + received_inputs: Option, + required_inputs: Option, + debounce_s: Option, + reason: Option, +} + +impl EventRow { + fn new( + subscriber_path: &str, + asset_kind: AssetKind, + asset_path: &str, + outcome: DispatchOutcome, + opts: EventOptions<'_>, + ) -> Self { + EventRow { + subscriber_path: subscriber_path.to_string(), + asset_kind, + asset_path: asset_path.to_string(), + outcome, + child_job_id: opts.child_job_id, + partition: opts.partition.map(str::to_string), + received_inputs: opts.received_inputs, + required_inputs: opts.required_inputs, + debounce_s: opts.debounce_s, + reason: opts.reason.map(str::to_string), + } + } +} + /// AND-join slot progress at the moment a partition-bearing input /// arrived. `fired` = all required inputs are now present (the slot has /// been cleared and the subscriber will be dispatched). `received` / @@ -120,19 +160,75 @@ struct JoinSlotStatus { required: i32, } -/// Best-effort insert into `dispatch_event`. Never propagates — the -/// dispatch contract is "logging failures must not retroactively fail -/// the producer's job." -async fn record_event( +/// Outcome of evaluating an AND-join barrier for one (subscriber, input). +/// The caller turns this into a `dispatch_event` row and decides whether to +/// push, all in one place. +enum JoinDecision { + /// Input does not advance the join (recorded as Skipped with `reason`). + Skip(&'static str), + /// Join advanced but is not yet complete (recorded as JoinPending). + Pending { received: i32, required: i32 }, + /// All required inputs are present — push the subscriber. + Fire, +} + +/// Evaluate the AND-join barrier for a partition-bearing subscriber input. +/// Only a partition-bearing input carrying a concrete partition advances the +/// join — a reference input or an unpartitioned producer must never fire a +/// partitioned join (the case-3 silent-wrong guard). On `Err` the caller +/// should log and skip without recording an event. +async fn handle_join( db: &DB, workspace_id: &str, - producer_job_id: Uuid, - subscriber_path: &str, - asset_kind: AssetKind, - asset_path: &str, - outcome: DispatchOutcome, - opts: EventOptions<'_>, -) { + sub_path: &str, + trigger_ref: &str, + partition: Option<&str>, +) -> Result { + if !is_partition_bearing_ref(trigger_ref) { + tracing::debug!( + "AND subscriber {}: non-partition-bearing input {} does not fire the join", + sub_path, + trigger_ref + ); + return Ok(JoinDecision::Skip("case3_non_partition_bearing")); + } + let Some(pv) = partition else { + tracing::warn!( + "AND subscriber {}: partition-bearing input {} arrived with no resolved \ + partition; not dispatching (case-3 guard)", + sub_path, + trigger_ref + ); + return Ok(JoinDecision::Skip("case3_missing_partition")); + }; + match record_and_check_join_slot(db, workspace_id, sub_path, pv, trigger_ref).await? { + JoinSlotStatus { fired: false, received, required } => { + Ok(JoinDecision::Pending { received, required }) + } + JoinSlotStatus { fired: true, .. } => Ok(JoinDecision::Fire), + } +} + +/// Best-effort batched insert into `dispatch_event`. Never propagates — the +/// dispatch contract is "logging failures must not retroactively fail the +/// producer's job." All rows accumulated over a dispatch pass go in one +/// INSERT (UNNEST) to avoid an N+1 across (subscriber × asset write). +async fn flush_events(db: &DB, workspace_id: &str, producer_job_id: Uuid, events: &[EventRow]) { + if events.is_empty() { + return; + } + // Column-oriented arrays for UNNEST. Each Vec is one column across all rows. + let subscriber_paths: Vec = events.iter().map(|e| e.subscriber_path.clone()).collect(); + let asset_kinds: Vec = events.iter().map(|e| e.asset_kind).collect(); + let asset_paths: Vec = events.iter().map(|e| e.asset_path.clone()).collect(); + let outcomes: Vec = events.iter().map(|e| e.outcome).collect(); + let child_job_ids: Vec> = events.iter().map(|e| e.child_job_id).collect(); + let partitions: Vec> = events.iter().map(|e| e.partition.clone()).collect(); + let received_inputs: Vec> = events.iter().map(|e| e.received_inputs).collect(); + let required_inputs: Vec> = events.iter().map(|e| e.required_inputs).collect(); + let debounce_s: Vec> = events.iter().map(|e| e.debounce_s).collect(); + let reasons: Vec> = events.iter().map(|e| e.reason.clone()).collect(); + let res = sqlx::query!( r#"INSERT INTO dispatch_event ( workspace_id, producer_job_id, subscriber_path, @@ -140,26 +236,31 @@ async fn record_event( child_job_id, partition, received_inputs, required_inputs, debounce_s, reason - ) VALUES ($1, $2, $3, $4::ASSET_KIND, $5, $6::DISPATCH_OUTCOME, - $7, $8, $9, $10, $11, $12)"#, + ) + SELECT $1, $2, sp, ak, ap, oc, cj, pt, ri, rq, db, rs + FROM unnest( + $3::text[], $4::ASSET_KIND[], $5::text[], $6::DISPATCH_OUTCOME[], + $7::uuid[], $8::text[], $9::int[], $10::int[], $11::int[], $12::text[] + ) AS t(sp, ak, ap, oc, cj, pt, ri, rq, db, rs)"#, workspace_id, producer_job_id, - subscriber_path, - asset_kind as AssetKind, - asset_path, - outcome as DispatchOutcome, - opts.child_job_id, - opts.partition, - opts.received_inputs, - opts.required_inputs, - opts.debounce_s, - opts.reason, + &subscriber_paths, + asset_kinds as Vec, + &asset_paths, + outcomes as Vec, + &child_job_ids as &[Option], + &partitions as &[Option], + &received_inputs as &[Option], + &required_inputs as &[Option], + &debounce_s as &[Option], + &reasons as &[Option], ) .execute(db) .await; if let Err(e) = res { tracing::error!( - "failed to record dispatch_event for producer {}: {e:#}", + "failed to record {} dispatch_event row(s) for producer {}: {e:#}", + events.len(), producer_job_id ); } @@ -220,8 +321,13 @@ async fn try_dispatch(db: &DB, job: &MiniCompletedJob) -> Result } let mut dispatched = Vec::new(); + // Best-effort dispatch_event rows accumulated over the whole pass and + // flushed in one batched INSERT at the end (avoids an N+1 over + // subscriber × asset write). The mid-pass join-slot writes + // (record_and_check_join_slot) are a separate table and unaffected. + let mut events: Vec = Vec::new(); for (asset_kind, asset_path) in writes { - let Some(prefix) = prefix_for(asset_kind) else { + let Some(prefix) = asset_kind.canonical_prefix() else { continue; }; let trigger_ref = format!("{}{}", prefix, asset_path); @@ -230,93 +336,51 @@ async fn try_dispatch(db: &DB, job: &MiniCompletedJob) -> Result let Subscriber { path: sub_path, join_all, debounce_s, retry_count, retry_delay_s } = sub; if sub_path == runnable_path { - record_event( - db, - &job.workspace_id, - job.id, + events.push(EventRow::new( &sub_path, asset_kind, &asset_path, DispatchOutcome::Skipped, EventOptions { reason: Some("self_loop"), ..Default::default() }, - ) - .await; + )); continue; } if join_all { - // AND join barrier. Only a partition-bearing input - // (`// on …/{partition}/…`) carrying a concrete partition - // advances the join — a reference input or an - // unpartitioned producer must never fire a partitioned - // join (the case-3 silent-wrong guard). - if !is_partition_bearing_ref(&trigger_ref) { - tracing::debug!( - "AND subscriber {}: non-partition-bearing input {} does not fire the join", - sub_path, - trigger_ref - ); - record_event( - db, - &job.workspace_id, - job.id, - &sub_path, - asset_kind, - &asset_path, - DispatchOutcome::Skipped, - EventOptions { - reason: Some("case3_non_partition_bearing"), - ..Default::default() - }, - ) - .await; - continue; - } - let Some(pv) = partition.as_deref() else { - tracing::warn!( - "AND subscriber {}: partition-bearing input {} arrived with no resolved \ - partition; not dispatching (case-3 guard)", - sub_path, - trigger_ref - ); - record_event( - db, - &job.workspace_id, - job.id, - &sub_path, - asset_kind, - &asset_path, - DispatchOutcome::Skipped, - EventOptions { - reason: Some("case3_missing_partition"), - ..Default::default() - }, - ) - .await; - continue; - }; - match record_and_check_join_slot(db, &job.workspace_id, &sub_path, pv, &trigger_ref) - .await + match handle_join( + db, + &job.workspace_id, + &sub_path, + &trigger_ref, + partition.as_deref(), + ) + .await { - Ok(JoinSlotStatus { fired: false, received, required }) => { - record_event( - db, - &job.workspace_id, - job.id, + Ok(JoinDecision::Skip(reason)) => { + events.push(EventRow::new( + &sub_path, + asset_kind, + &asset_path, + DispatchOutcome::Skipped, + EventOptions { reason: Some(reason), ..Default::default() }, + )); + continue; + } + Ok(JoinDecision::Pending { received, required }) => { + events.push(EventRow::new( &sub_path, asset_kind, &asset_path, DispatchOutcome::JoinPending, EventOptions { - partition: Some(pv), + partition: partition.as_deref(), received_inputs: Some(received), required_inputs: Some(required), ..Default::default() }, - ) - .await; + )); continue; // slot incomplete — wait for the rest } - Ok(JoinSlotStatus { fired: true, .. }) => {} // fall through to push + Ok(JoinDecision::Fire) => {} // fall through to push Err(e) => { tracing::error!("join-slot check failed for {}: {e:#}", sub_path); continue; @@ -339,10 +403,7 @@ async fn try_dispatch(db: &DB, job: &MiniCompletedJob) -> Result .await { Ok(id) => { - record_event( - db, - &job.workspace_id, - job.id, + events.push(EventRow::new( &sub_path, asset_kind, &asset_path, @@ -353,8 +414,7 @@ async fn try_dispatch(db: &DB, job: &MiniCompletedJob) -> Result debounce_s, ..Default::default() }, - ) - .await; + )); dispatched.push(id); } Err(e) => { @@ -364,6 +424,8 @@ async fn try_dispatch(db: &DB, job: &MiniCompletedJob) -> Result } } + flush_events(db, &job.workspace_id, job.id, &events).await; + if !dispatched.is_empty() { tracing::info!( "asset-trigger dispatch from job {} ({}): pushed {} downstream jobs", @@ -436,19 +498,6 @@ fn read_partition( serde_json::from_str::(trigger_map?.get(PARTITION_ARG)?.get()).ok() } -fn prefix_for(kind: AssetKind) -> Option<&'static str> { - match kind { - AssetKind::S3Object => Some("s3://"), - AssetKind::Resource => Some("$res:"), - AssetKind::Ducklake => Some("ducklake://"), - AssetKind::DataTable => Some("datatable://"), - AssetKind::Volume => Some("volume://"), - // Deprecated kind from before the parser was unified — has no - // canonical trigger ref and never produced trigger rows. - AssetKind::Variable => None, - } -} - async fn fetch_producer_writes( db: &Pool, workspace_id: &str, @@ -763,7 +812,7 @@ async fn push_subscriber( let mut args: HashMap> = HashMap::new(); let trigger_payload = serde_json::json!({ "kind": "asset", - "asset_kind": serde_json::to_value(&asset_kind).unwrap_or(serde_json::Value::Null), + "asset_kind": serde_json::to_value(&asset_kind).expect("AssetKind serializes"), "asset_path": asset_path, "producer_path": producer_path, "producer_job_id": producer.id.to_string(), diff --git a/backend/windmill-types/src/assets.rs b/backend/windmill-types/src/assets.rs index be20cfae3e..fb624382c4 100644 --- a/backend/windmill-types/src/assets.rs +++ b/backend/windmill-types/src/assets.rs @@ -16,6 +16,23 @@ pub enum AssetKind { Volume, } +impl AssetKind { + /// The canonical URI prefix used in asset trigger refs (e.g. `s3://`, + /// `$res:`). Single source of truth for both trigger-ref construction + /// and runtime cascade dispatch. `Variable` is deprecated and has no + /// canonical ref, so it returns `None`. + pub fn canonical_prefix(&self) -> Option<&'static str> { + match self { + AssetKind::S3Object => Some("s3://"), + AssetKind::Resource => Some("$res:"), + AssetKind::Ducklake => Some("ducklake://"), + AssetKind::DataTable => Some("datatable://"), + AssetKind::Volume => Some("volume://"), + AssetKind::Variable => None, + } + } +} + #[derive( Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type, PartialOrd, Ord, )] diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index fdef059c26..ab2cad4da9 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -784,19 +784,11 @@ pub async fn process_completed_job( // by resolve_partition_for_job). Run identity is immutable — // the preprocessor must not change or drop it, or the asset // cascade would read no partition for this producer. - sqlx::query!( - "UPDATE v2_job - SET args = CASE - WHEN args ? 'partition' - THEN $1 || jsonb_build_object('partition', args -> 'partition') - ELSE $1 - END, - preprocessed = TRUE - WHERE id = $2", - Json(preprocessed_args) as Json>>, - job.id + windmill_common::partition::merge_args_preserving_partition( + db, + job.id, + preprocessed_args, ) - .execute(db) .await?; } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 6e9a77597e..984acbe756 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -4385,14 +4385,7 @@ async fn resolve_partition_for_job( // Persist back so dispatch_asset_triggers (which reads the producer's // completed v2_job.args) propagates the same value down the cascade. if let Some(db) = conn.as_sql() { - sqlx::query!( - "UPDATE v2_job SET args = coalesce(args, '{}'::jsonb) \ - || jsonb_build_object('partition', $1::text) WHERE id = $2", - value, - job.id, - ) - .execute(db) - .await?; + windmill_common::partition::set_resolved_partition(db, job.id, &value).await?; } else { tracing::warn!( job_id = %job.id, diff --git a/cli/src/commands/pipeline/pipeline.ts b/cli/src/commands/pipeline/pipeline.ts index 27985af3c7..b4e08f6416 100644 --- a/cli/src/commands/pipeline/pipeline.ts +++ b/cli/src/commands/pipeline/pipeline.ts @@ -10,8 +10,13 @@ import * as log from "../../core/log.ts"; import { GlobalOptions } from "../../types.ts"; // Mirrors the asset-graph endpoint payload (backend/windmill-api-assets). -// Raw-fetched because these routes are newer than the checked-in generated -// client; swap to the generated functions on the next client regen. +// TODO: the checked-in generated client (cli/gen, last regenerated 2025-04) +// predates these routes, so we raw-fetch and hand-roll the types. Once +// `cli/gen` is regenerated (run `cli/gen_wm_client.sh`, which is currently +// >700 openapi.yaml commits stale and would churn the whole client), replace +// `apiGet` + these types with the generated `wmill.getAssetsGraph(...)` +// (operationId getAssetsGraph) and `wmill.listPipelineFolders(...)` +// (operationId listPipelineFolders). type GraphRunnable = { path: string; usage_kind: "script" | "flow" | "job"; @@ -92,6 +97,12 @@ function shortName(scriptPath: string): string { return scriptPath.split("/").pop() ?? scriptPath; } +// Append to a multimap value, creating the bucket on first use. Avoids the +// O(n^2) spread-rebuild pattern (`map.set(k, [...(map.get(k) ?? []), v])`). +function pushTo(map: Map, key: K, val: V): void { + (map.get(key) ?? map.set(key, []).get(key)!).push(val); +} + async function show( opts: GlobalOptions & { json?: boolean }, folder: string, @@ -121,10 +132,7 @@ async function show( for (const e of graph.edges) { if (e.access_type === "w" || e.access_type === "rw") { const uri = assetUri(e.asset_kind, e.asset_path); - writesByScript.set(e.runnable_path, [ - ...(writesByScript.get(e.runnable_path) ?? []), - uri, - ]); + pushTo(writesByScript, e.runnable_path, uri); } } const subsByAsset = new Map(); @@ -137,17 +145,15 @@ async function show( if (t.trigger_kind === "asset") { const at = t as Extract; const uri = assetUri(at.asset_kind, at.asset_path); - subsByAsset.set(uri, [...(subsByAsset.get(uri) ?? []), t.runnable_path]); - subsByScript.set(t.runnable_path, [ - ...(subsByScript.get(t.runnable_path) ?? []), - uri, - ]); + pushTo(subsByAsset, uri, t.runnable_path); + pushTo(subsByScript, t.runnable_path, uri); } else { const nt = t as Exclude; - nativeByScript.set(t.runnable_path, [ - ...(nativeByScript.get(t.runnable_path) ?? []), - { kind: nt.trigger_kind, path: nt.path, missing: nt.missing }, - ]); + pushTo(nativeByScript, t.runnable_path, { + kind: nt.trigger_kind, + path: nt.path, + missing: nt.missing, + }); } } @@ -205,9 +211,16 @@ async function show( .sort(); // UI-first markers (data_upload, webhook) have no trigger row — the - // graph endpoint can't surface them, they live as `// on ` - // annotations in the body. Roots are where sources matter, so fetch just - // those bodies and lift the marker kinds the canvas would show. + // graph endpoint's trigger enum (schedule/email/kafka/mqtt/nats/postgres/ + // sqs/gcp) can't surface them, so they only exist as `// on ` + // annotations in the script body. Roots are where sources matter, so fetch + // just those bodies and lift the marker kinds the canvas would show. + // + // DRIFT RISK: this regex + MARKER_KINDS is a divergent, partial copy of the + // canonical annotation parser. The proper fix is to have the graph endpoint + // emit these UI-only markers as trigger rows (a backend change), after which + // this whole Promise.all body-fetch can be deleted and read straight from + // the response. Until then, keep this list in sync with the canonical parser. const MARKER_KINDS = ["data_upload", "webhook", "email"]; await Promise.all( roots.map(async (p) => { diff --git a/data-pipelines-sync-demo.zip b/data-pipelines-sync-demo.zip deleted file mode 100644 index 9a2776993e2c079eec5b77fa7dabea2211dfc653..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14832 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

&RhI$H)EoPy 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 + import { workspaceStore } from '$lib/stores' + import { sendUserToast } from '$lib/utils' + import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte' + import type { NativeTriggerKind } from './types' + import { + EmailTriggerService, + GcpTriggerService, + KafkaTriggerService, + MqttTriggerService, + NatsTriggerService, + PostgresTriggerService, + ScheduleService, + SqsTriggerService + } from '$lib/gen' + import KafkaTriggerEditor from '$lib/components/triggers/kafka/KafkaTriggerEditor.svelte' + import MqttTriggerEditor from '$lib/components/triggers/mqtt/MqttTriggerEditor.svelte' + import NatsTriggerEditor from '$lib/components/triggers/nats/NatsTriggerEditor.svelte' + import PostgresTriggerEditor from '$lib/components/triggers/postgres/PostgresTriggerEditor.svelte' + import SqsTriggerEditor from '$lib/components/triggers/sqs/SqsTriggerEditor.svelte' + import GcpTriggerEditor from '$lib/components/triggers/gcp/GcpTriggerEditor.svelte' + import EmailTriggerEditor from '$lib/components/triggers/email/EmailTriggerEditor.svelte' + import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte' + import WebhookEditor from '$lib/components/triggers/webhook/WebhookEditor.svelte' + + // Owns the native-trigger drawer wiring for the pipeline canvas: the nine + // editor instances, the create/edit dispatch by kind, and the delete + // confirmation flow. The page drives it imperatively via `bind:this` so + // the draft-guard toasts (which depend on the page's drafts map) stay in + // the page while the editor plumbing lives here. + // + // `mountTriggerEditors` gates the eight editor instances: they're + // edit-mode-only on the canvas, so the page unmounts them off edit mode + // (matching the previous `{#if mode === 'edit'}` wrapper). The webhook + // editor stays mounted in every mode — its node is clickable in view mode + // too (informational endpoint URLs/token). + type Props = { onUpdate: () => void; mountTriggerEditors: boolean } + let { onUpdate, mountTriggerEditors }: Props = $props() + + let kafkaEditor: KafkaTriggerEditor | undefined = $state() + let mqttEditor: MqttTriggerEditor | undefined = $state() + let natsEditor: NatsTriggerEditor | undefined = $state() + let postgresEditor: PostgresTriggerEditor | undefined = $state() + let sqsEditor: SqsTriggerEditor | undefined = $state() + let gcpEditor: GcpTriggerEditor | undefined = $state() + let emailEditor: EmailTriggerEditor | undefined = $state() + let scheduleEditor: ScheduleEditor | undefined = $state() + let webhookEditor: WebhookEditor | undefined = $state() + + // Open the create-trigger drawer for `kind`, pre-filling `script_path`. + // Caller is responsible for the draft guard (a trigger row can only point + // at a deployed script). + export function openNew(kind: NativeTriggerKind, scriptPath: string) { + switch (kind) { + case 'schedule': + return scheduleEditor?.openNew(false, scriptPath, undefined, scriptPath) + case 'kafka': + return kafkaEditor?.openNew(false, scriptPath) + case 'mqtt': + return mqttEditor?.openNew(false, scriptPath) + case 'nats': + return natsEditor?.openNew(false, scriptPath) + case 'postgres': + return postgresEditor?.openNew(false, scriptPath) + case 'sqs': + return sqsEditor?.openNew(false, scriptPath) + case 'gcp': + return gcpEditor?.openNew(false, scriptPath) + case 'email': + return emailEditor?.openNew(false, scriptPath) + // webhook has no dedicated editor. + default: + return + } + } + + export function openEdit(kind: NativeTriggerKind, triggerPath: string, scriptPath: string) { + switch (kind) { + case 'schedule': + return scheduleEditor?.openEdit(triggerPath, false, scriptPath) + case 'kafka': + return kafkaEditor?.openEdit(triggerPath, false, scriptPath) + case 'mqtt': + return mqttEditor?.openEdit(triggerPath, false, scriptPath) + case 'nats': + return natsEditor?.openEdit(triggerPath, false, scriptPath) + case 'postgres': + return postgresEditor?.openEdit(triggerPath, false, scriptPath) + case 'sqs': + return sqsEditor?.openEdit(triggerPath, false, scriptPath) + case 'gcp': + return gcpEditor?.openEdit(triggerPath, false, scriptPath) + case 'email': + return emailEditor?.openEdit(triggerPath, false, scriptPath) + default: + return + } + } + + // Open the webhook drawer (endpoint URLs + token flow). Caller owns the + // draft guard. + export function openWebhook(scriptPath: string) { + webhookEditor?.openDrawer(scriptPath, false) + } + + // Delete-trigger confirmation state. Kebab → Delete opens the standard + // ConfirmationModal; the actual delete is dispatched from onConfirmed so + // the dialog stays consistent with the rest of the app. + let triggerDeleteTarget = $state<{ kind: NativeTriggerKind; path: string } | undefined>(undefined) + let triggerDeleteLoading = $state(false) + let triggerDeleteOpen = $derived(triggerDeleteTarget != undefined) + + export function requestDelete(kind: NativeTriggerKind, triggerPath: string) { + triggerDeleteTarget = { kind, path: triggerPath } + } + + async function confirmDeleteAttachedTrigger() { + if (!triggerDeleteTarget || !$workspaceStore) return + const { kind, path: triggerPath } = triggerDeleteTarget + const workspace = $workspaceStore + triggerDeleteLoading = true + try { + switch (kind) { + case 'schedule': + await ScheduleService.deleteSchedule({ workspace, path: triggerPath }) + break + case 'kafka': + await KafkaTriggerService.deleteKafkaTrigger({ workspace, path: triggerPath }) + break + case 'mqtt': + await MqttTriggerService.deleteMqttTrigger({ workspace, path: triggerPath }) + break + case 'nats': + await NatsTriggerService.deleteNatsTrigger({ workspace, path: triggerPath }) + break + case 'postgres': + await PostgresTriggerService.deletePostgresTrigger({ workspace, path: triggerPath }) + break + case 'sqs': + await SqsTriggerService.deleteSqsTrigger({ workspace, path: triggerPath }) + break + case 'gcp': + await GcpTriggerService.deleteGcpTrigger({ workspace, path: triggerPath }) + break + case 'email': + await EmailTriggerService.deleteEmailTrigger({ workspace, path: triggerPath }) + break + default: + return + } + sendUserToast(`Deleted ${kind} trigger "${triggerPath}"`) + triggerDeleteTarget = undefined + onUpdate() + } catch (e: any) { + sendUserToast( + `Could not delete ${kind} trigger "${triggerPath}": ${e?.body ?? e?.message ?? String(e)}`, + true + ) + } finally { + triggerDeleteLoading = false + } + } + + +{#if mountTriggerEditors} + { + if (!triggerDeleteLoading) triggerDeleteTarget = undefined + }} + > + {#if triggerDeleteTarget} +

+ Delete {triggerDeleteTarget.path}? The + // on {triggerDeleteTarget.kind} annotation on the script stays + — the trigger will read as missing on the canvas until you recreate it or remove the annotation. +

+ {/if} + + + + + + + + + + + +{/if} + + diff --git a/frontend/src/lib/components/assets/AssetGraph/deployGraphDiff.ts b/frontend/src/lib/components/assets/AssetGraph/deployGraphDiff.ts index 4d903278a0..3b3c7de6a1 100644 --- a/frontend/src/lib/components/assets/AssetGraph/deployGraphDiff.ts +++ b/frontend/src/lib/components/assets/AssetGraph/deployGraphDiff.ts @@ -1,4 +1,5 @@ import type { AssetGraphResponse } from './types' +import { assetKey, isWriteEdge } from './lib' // Post-deploy graph verification: the canvas previews a draft's edges from // the *frontend* parsers (resolveGraph overlay); the deployed rows are @@ -24,16 +25,14 @@ export type CascadeFacts = { export function extractCascadeFacts(g: AssetGraphResponse, scriptPath: string): CascadeFacts { const writes = new Set() for (const e of g.edges ?? []) { - if (e.runnable_kind !== 'script' || e.runnable_path !== scriptPath) continue - const access = e.access_type ?? 'r' - if (access !== 'w' && access !== 'rw') continue - writes.add(`${e.asset_kind}:${e.asset_path}`) + if (e.runnable_path !== scriptPath || !isWriteEdge(e)) continue + writes.add(assetKey(e)) } const subs = new Set() for (const t of g.triggers ?? []) { if (t.trigger_kind !== 'asset') continue if (t.runnable_kind !== 'script' || t.runnable_path !== scriptPath) continue - subs.add(`${t.asset_kind}:${t.asset_path}`) + subs.add(assetKey(t)) } return { writes, subs } } diff --git a/frontend/src/lib/components/assets/AssetGraph/graphTraversal.test.ts b/frontend/src/lib/components/assets/AssetGraph/graphTraversal.test.ts index b876882dc8..08da865d61 100644 --- a/frontend/src/lib/components/assets/AssetGraph/graphTraversal.test.ts +++ b/frontend/src/lib/components/assets/AssetGraph/graphTraversal.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from 'vitest' import type { AssetGraphResponse } from './types' -import { computeDownstreamClosure, getDownstreamSubscribers } from './graphTraversal' +import { buildDownstreamMap, computeDownstreamClosure } from './graphTraversal' + +/** Direct (one-hop) subscriber script paths of `scriptPath`. */ +function downstreamSubscribers(g: AssetGraphResponse, scriptPath: string): string[] { + return Array.from(buildDownstreamMap(g).get(scriptPath) ?? []) +} // Minimal graph builder: `writes` are producer→asset write edges, `subs` are // `// on ` subscriptions. All datatable/script for brevity. @@ -37,7 +42,7 @@ describe('buildDownstreamMap', () => { ['c', 'x'] ] ) - expect(getDownstreamSubscribers(g, 'a').sort()).toEqual(['b', 'c']) + expect(downstreamSubscribers(g, 'a').sort()).toEqual(['b', 'c']) }) it('ignores read edges, flow subscribers and self-loops', () => { @@ -57,8 +62,8 @@ describe('buildDownstreamMap', () => { runnable_kind: 'flow', runnable_path: 'f' }) - expect(getDownstreamSubscribers(g, 'a')).toEqual([]) - expect(getDownstreamSubscribers(g, 'r')).toEqual([]) + expect(downstreamSubscribers(g, 'a')).toEqual([]) + expect(downstreamSubscribers(g, 'r')).toEqual([]) }) it('matches on asset kind as well as path', () => { @@ -70,7 +75,7 @@ describe('buildDownstreamMap', () => { runnable_kind: 'script', runnable_path: 'b' }) - expect(getDownstreamSubscribers(g, 'a')).toEqual([]) + expect(downstreamSubscribers(g, 'a')).toEqual([]) }) }) diff --git a/frontend/src/lib/components/assets/AssetGraph/graphTraversal.ts b/frontend/src/lib/components/assets/AssetGraph/graphTraversal.ts index 736d5aa592..6b53020ddb 100644 --- a/frontend/src/lib/components/assets/AssetGraph/graphTraversal.ts +++ b/frontend/src/lib/components/assets/AssetGraph/graphTraversal.ts @@ -1,4 +1,5 @@ import type { AssetGraphResponse } from './types' +import { assetKey, buildAssetSubscribers, isWriteEdge } from './lib' // Execution-DAG traversal over the resolved asset graph (drafts included). // @@ -13,20 +14,11 @@ import type { AssetGraphResponse } from './types' /** `producer script path` → set of subscriber script paths (one hop). */ export function buildDownstreamMap(g: AssetGraphResponse): Map> { - const subscribersByAsset = new Map>() - for (const t of g.triggers ?? []) { - if (t.trigger_kind !== 'asset' || t.runnable_kind !== 'script') continue - const key = `${t.asset_kind}:${t.asset_path}` - const set = subscribersByAsset.get(key) ?? new Set() - set.add(t.runnable_path) - subscribersByAsset.set(key, set) - } + const subscribersByAsset = buildAssetSubscribers(g) const downstream = new Map>() for (const e of g.edges ?? []) { - if (e.runnable_kind !== 'script') continue - const access = e.access_type ?? 'r' - if (access !== 'w' && access !== 'rw') continue - const subs = subscribersByAsset.get(`${e.asset_kind}:${e.asset_path}`) + if (!isWriteEdge(e)) continue + const subs = subscribersByAsset.get(assetKey(e)) if (!subs) continue const merged = downstream.get(e.runnable_path) ?? new Set() for (const s of subs) if (s !== e.runnable_path) merged.add(s) @@ -35,11 +27,6 @@ export function buildDownstreamMap(g: AssetGraphResponse): Map` declarations). + * Flows are excluded, mirroring the backend dispatch policy. + */ +export function buildAssetSubscribers(g: AssetGraphResponse): Map> { + const subscribersByAsset = new Map>() + for (const t of g.triggers ?? []) { + if (t.trigger_kind !== 'asset' || t.runnable_kind !== 'script') continue + const key = assetKey(t) + const set = subscribersByAsset.get(key) ?? new Set() + set.add(t.runnable_path) + subscribersByAsset.set(key, set) + } + return subscribersByAsset +} diff --git a/frontend/src/lib/components/assets/AssetGraph/parsePipelineAnnotations.parity.test.ts b/frontend/src/lib/components/assets/AssetGraph/parsePipelineAnnotations.parity.test.ts index c03cfe1d78..dfb5385253 100644 --- a/frontend/src/lib/components/assets/AssetGraph/parsePipelineAnnotations.parity.test.ts +++ b/frontend/src/lib/components/assets/AssetGraph/parsePipelineAnnotations.parity.test.ts @@ -2,7 +2,23 @@ import { readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' -import { parsePipelineAnnotations } from './parsePipelineAnnotations' +import { parsePipelineAnnotations, type PipelineAnnotations } from './parsePipelineAnnotations' + +// Every field the TS parser produces, each with an assertion in the per-fixture +// test below. Typed as `Record` so adding a +// field to `PipelineAnnotations` is a compile error here until it's listed — +// forcing the author to also wire up its parity assertion. (Deploy-only Rust +// fields like join_mode / debounce_default are not part of this type, so they +// correctly never appear here.) +const ASSERTED_TS_FIELDS: Record = { + inPipeline: true, + triggerAssets: true, + nativeTriggers: true, + partition: true, + freshness: true, + tag: true, + retry: true +} // Parser-parity guard: this TS parser (drives the live graph preview) and // the Rust `parse_pipeline_annotations` (drives deploy) must stay @@ -14,8 +30,16 @@ import { parsePipelineAnnotations } from './parsePipelineAnnotations' // Rust counterpart: backend/parsers/windmill-parser/tests/ // pipeline_annotations_parity.rs. Extend the corpus when the grammar // changes; a fixture passing on one side and failing on the other is -// exactly the drift this exists to catch. Only fields both parsers produce -// are compared (join_mode / debounce_default are deploy-only, Rust-side). +// exactly the drift this exists to catch. +// +// Intentional divergence — the Rust parser is a superset. It also parses +// `join_mode` and `debounce_default`, which are DEPLOY-ONLY: they affect how +// the backend schedules cascade runs, never the rendered graph, so the TS +// preview parser deliberately doesn't produce them and they are not compared +// here. Every field the TS parser DOES produce is compared, and the +// `ASSERTED_TS_FIELDS` exhaustiveness check above fails the suite if a new TS +// field is added without a matching assertion — so a field can't be parsed on +// the TS side yet silently skipped by this guard. type Fixture = { name: string @@ -48,6 +72,18 @@ describe('parsePipelineAnnotations matches the shared Rust fixture corpus', () = expect(fixtures.length).toBeGreaterThan(0) }) + it('every field the parser emits across the corpus has a parity assertion', () => { + const asserted = new Set(Object.keys(ASSERTED_TS_FIELDS)) + const emitted = new Set() + for (const f of fixtures) { + for (const k of Object.keys(parsePipelineAnnotations(f.code))) emitted.add(k) + } + expect( + [...emitted].filter((k) => !asserted.has(k)), + 'unasserted parser fields' + ).toEqual([]) + }) + for (const f of fixtures) { it(f.name, () => { const got = parsePipelineAnnotations(f.code) diff --git a/frontend/src/lib/components/assets/AssetGraph/pipelineHistory.svelte.ts b/frontend/src/lib/components/assets/AssetGraph/pipelineHistory.svelte.ts index bfa316979e..6d69c564ab 100644 --- a/frontend/src/lib/components/assets/AssetGraph/pipelineHistory.svelte.ts +++ b/frontend/src/lib/components/assets/AssetGraph/pipelineHistory.svelte.ts @@ -46,6 +46,10 @@ export function usePipelineHistory( async function load(ws: string, prefix: string, days: number) { const myGen = ++gen + // A folder/days change (or unmount) bumps `gen`; once it diverges from + // this call's `myGen`, every write below this point belongs to a stale + // scope and must be dropped. + const isStale = () => gen !== myGen loading = true error = undefined try { @@ -64,7 +68,7 @@ export function usePipelineHistory( perPage: PER_PAGE, page: pages }) - if (gen !== myGen) return + if (isStale()) return for (const j of rows) { out.push({ id: j.id, @@ -89,15 +93,15 @@ export function usePipelineHistory( } catch (e) { console.warn('failed to load pipeline dispatch edges', e) } - if (gen !== myGen) return + if (isStale()) return events = out edges = edgeRows truncated = sawFullPage } catch (e: any) { - if (gen !== myGen) return + if (isStale()) return error = e?.body ?? e?.message ?? String(e) } finally { - if (gen === myGen) loading = false + if (!isStale()) loading = false } } diff --git a/frontend/src/lib/components/assets/AssetGraph/pipelineTemplates.ts b/frontend/src/lib/components/assets/AssetGraph/pipelineTemplates.ts index 3e09abc7c1..44e501ac3e 100644 --- a/frontend/src/lib/components/assets/AssetGraph/pipelineTemplates.ts +++ b/frontend/src/lib/components/assets/AssetGraph/pipelineTemplates.ts @@ -1,5 +1,6 @@ import type { ScriptLang, AssetKind } from '$lib/gen' import { random_adj } from '$lib/components/random_positive_adjetive' +import { parseDbInputFromAssetSyntax } from '$lib/utils' // What kind of asset the new script will produce. Drives the random output // path scheme and the body skeleton. The output asset is NOT declared in a @@ -77,8 +78,10 @@ const LANG_COMPATIBILITY: Record = { csharp: ['none'], graphql: ['none'], bunnative: ['none'], - nativets: ['none'] -} as any + nativets: ['none'], + ruby: ['none'], + rlang: ['none'] +} export function compatibleOutputKinds(lang: ScriptLang): PipelineOutputKind[] { return LANG_COMPATIBILITY[lang] ?? ['none'] @@ -182,9 +185,12 @@ function s3Key(path: string): string { } // Splits a datatable asset path (`/` or `/.
`) -// into its constituent parts. Mirrors `parseDbInputFromAssetSyntax` in -// $lib/utils.ts: schema is omitted when the table lives in the default -// (`public`) schema, prefixed with `.` only when explicit. +// into its constituent parts. The `.
` grammar is owned by +// `parseDbInputFromAssetSyntax` in $lib/utils.ts (which consumes a full +// `datatable://…` URI): we delegate to it for the schema/table split so the +// two stay in lockstep, and only keep the leading `/` extraction here — +// the util folds the db into `resourcePath`, which is awkward to read back, +// and it has no slashless fallback (the SQL emitters tolerate a bare db). function parseDatatablePath(p: string): { db: string schema: string | undefined @@ -193,10 +199,11 @@ function parseDatatablePath(p: string): { const slash = p.indexOf('/') if (slash < 0) return { db: p, schema: undefined, table: '' } const db = p.slice(0, slash) - const tail = p.slice(slash + 1) - const dot = tail.indexOf('.') - if (dot < 0) return { db, schema: undefined, table: tail } - return { db, schema: tail.slice(0, dot), table: tail.slice(dot + 1) } + // `datatable://` URIs always parse to the `database` variant, so the + // `specificSchema`/`specificTable` accessors below are always present. + const parsed = parseDbInputFromAssetSyntax(`datatable://${p}`) + const schema = parsed?.type === 'database' ? parsed.specificSchema : undefined + return { db, schema, table: parsed?.specificTable ?? '' } } // Schema-qualified table reference for SQL emission. We always emit the diff --git a/frontend/src/lib/components/assets/AssetGraph/resolveGraph.ts b/frontend/src/lib/components/assets/AssetGraph/resolveGraph.ts index d6067a347a..6e4f512000 100644 --- a/frontend/src/lib/components/assets/AssetGraph/resolveGraph.ts +++ b/frontend/src/lib/components/assets/AssetGraph/resolveGraph.ts @@ -35,6 +35,67 @@ export type ResolveGraphInput = { annotatedNativeKindsByPath: Map> } +/** Mutable bag the sequential passes accumulate the resolved graph into. */ +type Accumulator = { + runnables: AssetGraphResponse['runnables'] + assets: AssetGraphResponse['assets'] + edges: AssetGraphResponse['edges'] + extraTriggers: AssetGraphResponse['triggers'] +} + +/** + * Native trigger kinds with a persisted (non-asset) trigger row pointing at + * `path`. These bind by script path and survive content edits, so they dedup + * against draft/live annotations to avoid showing a real source node next to a + * red "missing" placeholder for the same kind. + */ +function persistedNativeKinds(base: AssetGraphResponse, path: string): Set { + return new Set( + base.triggers + .filter( + (t) => + t.trigger_kind !== 'asset' && t.runnable_kind === 'script' && t.runnable_path === path + ) + .map((t) => t.trigger_kind) + ) +} + +/** `kind:path` keys of persisted asset (`// on `) triggers for `path`. */ +function persistedAssetKeys(base: AssetGraphResponse, path: string): Set { + return new Set( + base.triggers + .filter( + (t) => + t.trigger_kind === 'asset' && t.runnable_kind === 'script' && t.runnable_path === path + ) + .map((t) => (t.trigger_kind === 'asset' ? `${t.asset_kind}:${t.asset_path}` : '')) + ) +} + +/** + * Emit a red "missing" placeholder for each annotated native kind that has no + * matching attached trigger row. `opts.unsaved` marks editor-driven overlays + * (drafts / live buffer); deployed-but-unswept scripts pass `unsaved: false`. + */ +function pushMissingNativeTriggers( + extraTriggers: AssetGraphResponse['triggers'], + annotatedKinds: Iterable, + attachedKinds: Set, + path: string, + opts: { unsaved: boolean } +) { + for (const kind of annotatedKinds) { + if (attachedKinds.has(kind)) continue + extraTriggers.push({ + trigger_kind: kind, + runnable_kind: 'script', + runnable_path: path, + ...(opts.unsaved ? { unsaved: true } : {}), + missing: true + }) + } +} + /** * Merge the persisted base graph with the draft, session-inferred and * open-script live overlays into one `AssetGraphResponse`. @@ -50,41 +111,61 @@ export type ResolveGraphInput = { * `graphWithDraft` `$derived`. See `resolveGraph.test.ts`. */ export function resolveGraph(input: ResolveGraphInput): AssetGraphResponse { - const { - base, - drafts, - liveBodyAssets, - liveAnnotations, - inferredWritesByPath, - inferredReadsByPath, - annotatedNativeKindsByPath - } = input + const { base } = input + const ctx = makeContext(input) + + // Pass order mirrors the precedence comment, lowest → highest: + const acc = seedAccumulator(input, ctx) + seedDraftOverlays(acc, input) + applyLiveBufferOverlay(acc, input, ctx) + crossCheckSweptScripts(acc, input) + overlayInferredLineage(acc, input) + + // Drop persisted ASSET triggers for drafted paths — those come from the + // deployed `// on ` annotations, which the draft's live/parsed + // annotations now own. Native triggers (kafka/schedule/…) are kept: they + // bind by `script_path`, which the draft shares, so the attachment is still + // valid regardless of content edits. + const baseTriggers = base.triggers.filter((t) => { + if (t.trigger_kind !== 'asset') return true + if (ctx.isDrafted(t.runnable_kind, t.runnable_path)) return false + if ( + t.runnable_kind === 'script' && + t.runnable_path === ctx.openPath && + ctx.staleForOpen(t.asset_kind, t.asset_path) + ) + return false + return true + }) + return { + ...base, + assets: acc.assets, + runnables: acc.runnables, + edges: acc.edges, + triggers: [...baseTriggers, ...acc.extraTriggers] + } +} + +type ResolveContext = { + draftedPaths: Set + isDrafted: (kind: string, p: string) => boolean + openPath: string | undefined + staleForOpen: (kind: AssetKind, path: string) => boolean +} + +/** Derive the shared "what's drafted / open / stale" predicates once. */ +function makeContext(input: ResolveGraphInput): ResolveContext { + const { drafts, liveBodyAssets, liveAnnotations } = input - // Every draft contributes: a runnable, an output asset, a write edge, - // plus its own seeded schedule trigger (template includes `// on - // schedule "0 * * * *"` by default, picked up through live parse). - // We iterate the whole `drafts` map so multiple concurrent drafts - // all render as their own subgraph at once. - const runnables = [...base.runnables] - const assets = [...base.assets] // A drafted path's content-derived lineage is owned entirely by the draft // overlay (live inference for the active draft, parsed annotations / - // snapshot for inactive ones). Drop the persisted base asset edges for those + // snapshot for inactive ones). Drop persisted base asset edges for those // paths so it shows only the draft's I/O, not the union of the saved version // and the in-flight edits. (Base asset triggers are dropped at the return; // native triggers are kept — they bind by path.) const draftedPaths = new Set(drafts.keys()) const isDrafted = (kind: string, p: string) => kind === 'script' && draftedPaths.has(p) - // Editing a *saved* script does NOT create a `drafts` entry — it stays an - // open selection with live overlays. So the draft filter above doesn't cover - // it: its persisted edges/triggers would linger next to the live edits (e.g. - // renaming an input leaves the old asset linked alongside the new one). For - // the open, live-inferred saved script, treat its current content as - // authoritative and drop persisted asset edges/triggers it no longer - // references. `liveRefKeys` unions the reliable `// on ` annotations - // with the inferred body assets, so an unchanged selection drops nothing - // (every base asset is still referenced). const openPath = liveBodyAssets.scriptPath const openIsSavedEdit = openPath !== undefined && !draftedPaths.has(openPath) const liveRefKeys = new Set() @@ -98,17 +179,39 @@ export function resolveGraph(input: ResolveGraphInput): AssetGraphResponse { const staleForOpen = (kind: AssetKind, path: string) => openIsSavedEdit && !liveRefKeys.has(`${kind}:${path}`) + return { draftedPaths, isDrafted, openPath, staleForOpen } +} + +/** Base graph plus the persisted edges that survive the draft/open filter. */ +function seedAccumulator(input: ResolveGraphInput, ctx: ResolveContext): Accumulator { + const { base } = input const edges = base.edges.filter((e) => { - if (isDrafted(e.runnable_kind, e.runnable_path)) return false + if (ctx.isDrafted(e.runnable_kind, e.runnable_path)) return false if ( e.runnable_kind === 'script' && - e.runnable_path === openPath && - staleForOpen(e.asset_kind, e.asset_path) + e.runnable_path === ctx.openPath && + ctx.staleForOpen(e.asset_kind, e.asset_path) ) return false return true }) - const extraTriggers: AssetGraphResponse['triggers'] = [] + return { + runnables: [...base.runnables], + assets: [...base.assets], + edges, + extraTriggers: [] + } +} + +/** + * Every draft contributes: a runnable, output asset(s), a write edge, live + * read lineage for the active draft, plus its seeded asset/native triggers + * (template includes `// on schedule …` by default). Iterates the whole + * `drafts` map so multiple concurrent drafts all render at once. + */ +function seedDraftOverlays(acc: Accumulator, input: ResolveGraphInput) { + const { base, drafts, liveBodyAssets } = input + const { runnables, assets, edges, extraTriggers } = acc for (const [path, d] of drafts) { const parsed = parsePipelineAnnotations(d.script.content) @@ -238,140 +341,110 @@ export function resolveGraph(input: ResolveGraphInput): AssetGraphResponse { // they bind by path and are not dropped above. Dedup against those // so the canvas doesn't show the real schedule node next to a red // "missing" placeholder for the same kind. - const persistedNativeKinds = new Set( - base.triggers - .filter( - (t) => - t.trigger_kind !== 'asset' && - t.runnable_kind === 'script' && - t.runnable_path === path - ) - .map((t) => t.trigger_kind) + pushMissingNativeTriggers( + extraTriggers, + parsed.nativeTriggers.map((n) => n.kind), + persistedNativeKinds(base, path), + path, + { unsaved: true } ) - for (const n of parsed.nativeTriggers) { - if (persistedNativeKinds.has(n.kind)) continue - extraTriggers.push({ - trigger_kind: n.kind, - runnable_kind: 'script', - runnable_path: path, - unsaved: true, - missing: true - }) - } } +} - // Live-parsed overlay for the currently-open script — takes precedence - // over the seeded-template triggers for the same path by swapping - // them out. Scoped to one path (only one pane is open at a time). +/** + * Live-parsed overlay for the currently-open script — takes precedence over + * the seeded-template triggers for the same path by swapping them out. Scoped + * to one path (only one pane is open at a time). + */ +function applyLiveBufferOverlay(acc: Accumulator, input: ResolveGraphInput, ctx: ResolveContext) { + const { base, liveAnnotations } = input + const { assets, extraTriggers } = acc const livePath = liveAnnotations.scriptPath - if (livePath) { - // When the open buffer is a draft, its base asset triggers were dropped - // above (draft-owned), so don't dedup live asset triggers against them — - // every live `// on ` is emitted as unsaved. For a non-draft open - // script (viewing a saved one), keep deduping so live re-parse doesn't - // double the persisted triggers. - const persistedAssetKeys = draftedPaths.has(livePath) - ? new Set() - : new Set( - base.triggers - .filter( - (t) => - t.trigger_kind === 'asset' && - t.runnable_kind === 'script' && - t.runnable_path === livePath - ) - .map((t) => (t.trigger_kind === 'asset' ? `${t.asset_kind}:${t.asset_path}` : '')) - ) - // Strip seeded triggers we computed above for the active draft; - // live annotations are authoritative for the open buffer. - for (let i = extraTriggers.length - 1; i >= 0; i--) { - if (extraTriggers[i].runnable_path === livePath) extraTriggers.splice(i, 1) - } - for (const a of liveAnnotations.annotations.triggerAssets) { - const key = `${a.kind}:${a.path}` - if (persistedAssetKeys.has(key)) continue - extraTriggers.push({ - trigger_kind: 'asset', - asset_kind: a.kind, - asset_path: a.path, - runnable_kind: 'script', - runnable_path: livePath, - unsaved: true - }) - // Synthesize the asset node so the new trigger edge has a - // target — without this, typing `// on s3:///...` adds an - // edge to a node that doesn't exist and the canvas silently - // drops it. Mirrors the draft branch above. - if (!assets.some((x) => x.kind === a.kind && x.path === a.path)) { - assets.push({ kind: a.kind, path: a.path }) - } - } - // Native trigger annotations: kinds for which a matching trigger - // row was found in the backend response. If the live buffer - // declares `// on kafka` and at least one kafka_trigger row points - // at this script, the source node is already on the canvas — no - // overlay needed. Otherwise emit a "missing" placeholder so the - // user can either create the trigger row or remove the annotation. - const persistedNativeKinds = new Set( - base.triggers - .filter( - (t) => - t.trigger_kind !== 'asset' && - t.runnable_kind === 'script' && - t.runnable_path === livePath - ) - .map((t) => t.trigger_kind) - ) - for (const n of liveAnnotations.annotations.nativeTriggers) { - if (persistedNativeKinds.has(n.kind)) continue - extraTriggers.push({ - trigger_kind: n.kind, - runnable_kind: 'script', - runnable_path: livePath, - unsaved: true, - missing: true - }) + if (!livePath) return + + // When the open buffer is a draft, its base asset triggers were dropped + // above (draft-owned), so don't dedup live asset triggers against them — + // every live `// on ` is emitted as unsaved. For a non-draft open + // script (viewing a saved one), keep deduping so live re-parse doesn't + // double the persisted triggers. + const assetKeys = ctx.draftedPaths.has(livePath) + ? new Set() + : persistedAssetKeys(base, livePath) + // Strip seeded triggers we computed above for the active draft; + // live annotations are authoritative for the open buffer. + for (let i = extraTriggers.length - 1; i >= 0; i--) { + if (extraTriggers[i].runnable_path === livePath) extraTriggers.splice(i, 1) + } + for (const a of liveAnnotations.annotations.triggerAssets) { + const key = `${a.kind}:${a.path}` + if (assetKeys.has(key)) continue + extraTriggers.push({ + trigger_kind: 'asset', + asset_kind: a.kind, + asset_path: a.path, + runnable_kind: 'script', + runnable_path: livePath, + unsaved: true + }) + // Synthesize the asset node so the new trigger edge has a + // target — without this, typing `// on s3:///...` adds an + // edge to a node that doesn't exist and the canvas silently + // drops it. Mirrors the draft branch above. + if (!assets.some((x) => x.kind === a.kind && x.path === a.path)) { + assets.push({ kind: a.kind, path: a.path }) } } + // Native trigger annotations: kinds for which a matching trigger + // row was found in the backend response. If the live buffer + // declares `// on kafka` and at least one kafka_trigger row points + // at this script, the source node is already on the canvas — no + // overlay needed. Otherwise emit a "missing" placeholder so the + // user can either create the trigger row or remove the annotation. + pushMissingNativeTriggers( + extraTriggers, + liveAnnotations.annotations.nativeTriggers.map((n) => n.kind), + persistedNativeKinds(base, livePath), + livePath, + { unsaved: true } + ) +} - // Cross-check for already-deployed scripts (not the open buffer): if a - // script's persisted body declares `// on kafka` but no matching - // kafka_trigger row points at it, surface a red placeholder. The - // annotated-kinds map is filled by the page-level prefetch sweep - // (one read per script in the folder); drafts and the active editor - // are handled by the loops above. Scripts that haven't been swept - // yet contribute nothing here — they'll surface on the next refetch. - const livePathExcl = livePath +/** + * Cross-check for already-deployed scripts (not the open buffer): if a + * script's persisted body declares `// on kafka` but no matching kafka_trigger + * row points at it, surface a red placeholder. The annotated-kinds map is + * filled by the page-level prefetch sweep (one read per script in the folder); + * drafts and the active editor are handled by the earlier passes. Scripts not + * yet swept contribute nothing — they surface on the next refetch. + */ +function crossCheckSweptScripts(acc: Accumulator, input: ResolveGraphInput) { + const { base, drafts, liveAnnotations, annotatedNativeKindsByPath } = input + const livePath = liveAnnotations.scriptPath for (const [scriptPath, kinds] of annotatedNativeKindsByPath) { if (drafts.has(scriptPath)) continue - if (scriptPath === livePathExcl) continue - const attachedKinds = new Set( - base.triggers - .filter( - (t) => - t.trigger_kind !== 'asset' && - t.runnable_kind === 'script' && - t.runnable_path === scriptPath - ) - .map((t) => t.trigger_kind) + if (scriptPath === livePath) continue + pushMissingNativeTriggers( + acc.extraTriggers, + kinds, + persistedNativeKinds(base, scriptPath), + scriptPath, + { unsaved: false } ) - for (const kind of kinds) { - if (attachedKinds.has(kind)) continue - extraTriggers.push({ - trigger_kind: kind, - runnable_kind: 'script', - runnable_path: scriptPath, - missing: true - }) - } } +} + +/** + * Live body-asset lineage for any persisted script inferred at least once this + * session (maps filled by `handleAssetsChange` + the load prefetch). Drafts + * are handled by `seedDraftOverlays`. For scripts whose deploy didn't persist + * their body assets (e.g. older WASM at save time, or object-form + * writeS3File), this keeps the lineage edge on the canvas across selection + * changes — not just while selected. + */ +function overlayInferredLineage(acc: Accumulator, input: ResolveGraphInput) { + const { base, drafts, inferredWritesByPath, inferredReadsByPath } = input + const { assets, edges } = acc - // Live body-asset lineage for any persisted script inferred at least - // once this session (maps filled by `handleAssetsChange` + the load - // prefetch). Drafts are handled by the loop above. For scripts whose - // deploy didn't persist their body assets (e.g. older WASM at save - // time, or object-form writeS3File), this keeps the lineage edge on - // the canvas across selection changes — not just while selected. const overlayLineage = ( byPath: Map>, access: 'w' | 'r' @@ -407,22 +480,4 @@ export function resolveGraph(input: ResolveGraphInput): AssetGraphResponse { } overlayLineage(inferredWritesByPath, 'w') overlayLineage(inferredReadsByPath, 'r') - - // Drop persisted ASSET triggers for drafted paths — those come from the - // deployed `// on ` annotations, which the draft's live/parsed - // annotations now own. Native triggers (kafka/schedule/…) are kept: they - // bind by `script_path`, which the draft shares, so the attachment is still - // valid regardless of content edits. - const baseTriggers = base.triggers.filter((t) => { - if (t.trigger_kind !== 'asset') return true - if (isDrafted(t.runnable_kind, t.runnable_path)) return false - if ( - t.runnable_kind === 'script' && - t.runnable_path === openPath && - staleForOpen(t.asset_kind, t.asset_path) - ) - return false - return true - }) - return { ...base, assets, runnables, edges, triggers: [...baseTriggers, ...extraTriggers] } } diff --git a/frontend/src/lib/components/runs/DispatchEventsButton.svelte b/frontend/src/lib/components/runs/DispatchEventsButton.svelte index 19455e18de..8edae8efc6 100644 --- a/frontend/src/lib/components/runs/DispatchEventsButton.svelte +++ b/frontend/src/lib/components/runs/DispatchEventsButton.svelte @@ -1,8 +1,7 @@ {#if list.length > 0} diff --git a/frontend/src/lib/components/runs/DispatchEventsPanel.svelte b/frontend/src/lib/components/runs/DispatchEventsPanel.svelte index 545ff35488..5f9ee2d582 100644 --- a/frontend/src/lib/components/runs/DispatchEventsPanel.svelte +++ b/frontend/src/lib/components/runs/DispatchEventsPanel.svelte @@ -1,18 +1,16 @@ {#if list.length > 0} diff --git a/frontend/src/lib/components/runs/DispatchEventsTable.svelte b/frontend/src/lib/components/runs/DispatchEventsTable.svelte index 1ac85b990e..b095c9bda7 100644 --- a/frontend/src/lib/components/runs/DispatchEventsTable.svelte +++ b/frontend/src/lib/components/runs/DispatchEventsTable.svelte @@ -7,6 +7,9 @@ type Props = { events: Event[]; workspace: string } let { events, workspace }: Props = $props() + // These reason strings are produced verbatim by the backend asset-dispatch + // reason enum (backend asset_dispatch.rs) — keep them in sync if that enum + // changes; an unmatched reason falls through to the raw string. function reasonLabel(reason: string | undefined): string { switch (reason) { case 'self_loop': diff --git a/frontend/src/lib/components/runs/useDispatchEvents.svelte.ts b/frontend/src/lib/components/runs/useDispatchEvents.svelte.ts new file mode 100644 index 0000000000..f9a148f7f4 --- /dev/null +++ b/frontend/src/lib/components/runs/useDispatchEvents.svelte.ts @@ -0,0 +1,24 @@ +import { resource } from 'runed' +import { JobService, type ListDispatchEventsResponse } from '$lib/gen' + +export type DispatchEvent = ListDispatchEventsResponse[number] + +// Shared loader for "the jobs this run dispatched". Both the inline button and +// the run-detail panel render the same list, so keep the fetch in one place. +// Returns a getter for the (never-undefined) event list — empty while loading, +// when there's nothing to load, or before workspace/jobId are known. +export function useDispatchEvents( + workspace: () => string, + jobId: () => string +): { readonly list: DispatchEvent[] } { + const events = resource( + () => ({ workspace: workspace(), jobId: jobId() }), + async ({ workspace, jobId }) => + workspace && jobId ? await JobService.listDispatchEvents({ workspace, id: jobId }) : [] + ) + return { + get list() { + return events.current ?? [] + } + } +} diff --git a/frontend/src/lib/components/triggers/TriggerRunnablePicker.svelte b/frontend/src/lib/components/triggers/TriggerRunnablePicker.svelte new file mode 100644 index 0000000000..208645182f --- /dev/null +++ b/frontend/src/lib/components/triggers/TriggerRunnablePicker.svelte @@ -0,0 +1,59 @@ + + +{#if fixedScriptPath != ''} + +{:else} +

+ {promptText} +

+
+ + {@render createButton?.()} +
+{/if} diff --git a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte index 2f146831b3..34cf4e92c3 100644 --- a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte @@ -3,9 +3,7 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import Path from '$lib/components/Path.svelte' - import Required from '$lib/components/Required.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { EmailTriggerService, type ErrorHandler, @@ -240,8 +238,8 @@ return { noDeployed: !!(s as any)?.no_deployed, overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined } } @@ -390,38 +388,27 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
-
- - - {#if emptyString(script_path)} - - {/if} -
-
- {/if} + + {#snippet createButton()} + {#if emptyString(script_path)} + + {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte index 356983b1fc..f2a22c359c 100644 --- a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte @@ -18,9 +18,7 @@ type TriggerMode } from '$lib/gen' import Section from '$lib/components/Section.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' - import Required from '$lib/components/Required.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import GcpTriggerEditorConfigSection from './GcpTriggerEditorConfigSection.svelte' import { untrack, type Snippet } from 'svelte' import TriggerEditorToolbar from '../TriggerEditorToolbar.svelte' @@ -213,11 +211,11 @@ const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any loadTriggerConfig(deployedTrigger) return { - noDeployed: !!(s as any)?.no_deployed, - overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined - } + noDeployed: !!(s as any)?.no_deployed, + overlay: draftFromBackend + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined + } } catch (error) { sendUserToast(`Could not load GCP Pub/Sub trigger: ${error.body}`, true) return { overlay: undefined, noDeployed: false } @@ -477,24 +475,16 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- + + {#snippet createButton()} {#if emptyString(script_path)} {/if} -
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte index b20e457cd9..afdd9d7746 100644 --- a/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte @@ -4,9 +4,7 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import Path from '$lib/components/Path.svelte' - import Required from '$lib/components/Required.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { KafkaTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen' import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores' import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils' @@ -271,8 +269,8 @@ return { noDeployed: !!(s as any)?.no_deployed, overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined } } @@ -525,24 +523,15 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- + + {#snippet createButton()} {#if emptyString(script_path)} {/if} -
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte index 469bc0419d..561608bd9f 100644 --- a/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte @@ -4,9 +4,7 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import Path from '$lib/components/Path.svelte' - import Required from '$lib/components/Required.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores' import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils' import { withForkConflictRetry } from '$lib/utils/forkConflict' @@ -269,11 +267,11 @@ const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any loadTriggerConfig(deployedTrigger) return { - noDeployed: !!(s as any)?.no_deployed, - overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined - } + noDeployed: !!(s as any)?.no_deployed, + overlay: draftFromBackend + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined + } } catch (error) { sendUserToast(`Could not load mqtt trigger: ${error.body}`, true) return { overlay: undefined, noDeployed: false } @@ -500,24 +498,15 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- + + {#snippet createButton()} {#if emptyString(script_path)}
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte index d184fb65cf..4450cd6891 100644 --- a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte @@ -3,9 +3,7 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import Path from '$lib/components/Path.svelte' - import Required from '$lib/components/Required.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { NatsTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen' import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores' import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils' @@ -267,8 +265,8 @@ return { noDeployed: !!(s as any)?.no_deployed, overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined } } @@ -495,24 +493,15 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- + + {#snippet createButton()} {#if emptyString(script_path)}
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte index fad6fe18f2..6420d5f9b1 100644 --- a/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte @@ -4,8 +4,7 @@ import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import Path from '$lib/components/Path.svelte' import Required from '$lib/components/Required.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { PostgresTriggerService, type ErrorHandler, @@ -672,25 +671,17 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- - + + {#snippet createButton()} {#if emptyString(script_path) && is_flow === false}
{/if} -
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte index a8f25c7167..6ca2d13ddd 100644 --- a/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte @@ -17,9 +17,7 @@ } from '$lib/gen' import SqsTriggerEditorConfigSection from './SqsTriggerEditorConfigSection.svelte' import Section from '$lib/components/Section.svelte' - import ScriptPicker from '$lib/components/ScriptPicker.svelte' - import PipelineLockedRunnableInfo from '$lib/components/triggers/PipelineLockedRunnableInfo.svelte' - import Required from '$lib/components/Required.svelte' + import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte' import { untrack, type Snippet } from 'svelte' import TriggerEditorToolbar from '../TriggerEditorToolbar.svelte' import PermissionedAsLine from '../PermissionedAsLine.svelte' @@ -247,11 +245,11 @@ const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any loadTriggerConfig(deployedTrigger) return { - noDeployed: !!(s as any)?.no_deployed, - overlay: draftFromBackend - ? ({ ...deployedTrigger, ...draftFromBackend } as Record) - : undefined - } + noDeployed: !!(s as any)?.no_deployed, + overlay: draftFromBackend + ? ({ ...deployedTrigger, ...draftFromBackend } as Record) + : undefined + } } catch (error) { sendUserToast(`Could not load SQS trigger: ${error.body}`, true) return { overlay: undefined, noDeployed: false } @@ -479,24 +477,16 @@ {#if !hideTarget}
- {#if fixedScriptPath != ''} - - {:else} -

- Pick a script or flow to be triggered -

-
- + + {#snippet createButton()} {#if emptyString(script_path)}
- {/if} + {/snippet} +
{/if} diff --git a/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte b/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte index ef133de83d..61b08d52c6 100644 --- a/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte @@ -19,7 +19,6 @@ import PipelineActivityPanel from '$lib/components/assets/AssetGraph/PipelineActivityPanel.svelte' import AssetGraphDetailsPane from '$lib/components/assets/AssetGraph/AssetGraphDetailsPane.svelte' import PipelinePickerModal from '$lib/components/assets/AssetGraph/PipelinePickerModal.svelte' - import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte' import { extractWrites, extractReads, @@ -71,17 +70,9 @@ Telescope } from 'lucide-svelte' import { - EmailTriggerService, - GcpTriggerService, JobService, - KafkaTriggerService, - MqttTriggerService, - NatsTriggerService, OpenAPI, - PostgresTriggerService, - ScheduleService, ScriptService, - SqsTriggerService, type AssetKind, type Script, type ScriptLang @@ -95,15 +86,7 @@ import Popover from '$lib/components/meltComponents/Popover.svelte' import HideButton from '$lib/components/apps/editor/settingsPanel/HideButton.svelte' import { inferArgs, inferAssets } from '$lib/infer' - import KafkaTriggerEditor from '$lib/components/triggers/kafka/KafkaTriggerEditor.svelte' - import MqttTriggerEditor from '$lib/components/triggers/mqtt/MqttTriggerEditor.svelte' - import NatsTriggerEditor from '$lib/components/triggers/nats/NatsTriggerEditor.svelte' - import PostgresTriggerEditor from '$lib/components/triggers/postgres/PostgresTriggerEditor.svelte' - import SqsTriggerEditor from '$lib/components/triggers/sqs/SqsTriggerEditor.svelte' - import GcpTriggerEditor from '$lib/components/triggers/gcp/GcpTriggerEditor.svelte' - import EmailTriggerEditor from '$lib/components/triggers/email/EmailTriggerEditor.svelte' - import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte' - import WebhookEditor from '$lib/components/triggers/webhook/WebhookEditor.svelte' + import PipelineTriggerEditors from '$lib/components/assets/AssetGraph/PipelineTriggerEditors.svelte' // Variables and resources are declarative config, not pipeline assets — // they're hub-shaped (referenced by most runnables) and would swamp the @@ -128,10 +111,7 @@ selection = undefined activeDraftPath = undefined panelHidden = false - liveAnnotations = { - scriptPath: undefined, - annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } - } + liveAnnotations = EMPTY_LIVE_ANNOTATIONS } const url = new URL(page.url) if (m === 'view') url.searchParams.delete('mode') @@ -242,10 +222,7 @@ panelHidden = false // Same reset as the pane's close button — clears the live // annotation overlay of whichever script was open. - liveAnnotations = { - scriptPath: undefined, - annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } - } + liveAnnotations = EMPTY_LIVE_ANNOTATIONS } } @@ -503,6 +480,24 @@ content: '' }) + // Canonical "empty" overlay literals, reused both as reset values for the + // live-* state above and as the no-overlay inputs to the deployed graph. + const EMPTY_LIVE_ASSETS = { scriptPath: undefined, assets: [] } + const EMPTY_LIVE_ANNOTATIONS = { + scriptPath: undefined, + annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } + } + + // Reset every live editor overlay (annotations / body assets / content) + // back to empty, unconditionally. Used by the leave-edit path so a stale + // buffer for the previously-open script can't leak into the view graphs. + // (forgetPath resets these per-path instead — see there.) + function clearLiveOverlays() { + liveAnnotations = EMPTY_LIVE_ANNOTATIONS + liveBodyAssets = EMPTY_LIVE_ASSETS + liveContent = { scriptPath: undefined, content: '' } + } + // Only-add cache of (script_path → body content) populated lazily by // `bodyFetchEffect` for every script in the current folder. We never // remove entries: stale keys (renamed-away, deleted) are simply ignored @@ -524,10 +519,15 @@ // Iteration is gated on `graphRes.current.runnables`, so a path that // gets renamed / deleted disappears from the derived map as soon as // the refetch lands — no manual rekey, no phantom edges. - let inferredWritesByPath = $derived.by(() => { - const out = new Map>() + // Single pass over the graph's scripts producing both the write- and + // read-asset maps (they only differ by extractWrites vs extractReads over + // the same `liveBodyAssets`-vs-cache asset source). Split into two derives + // below so consumers can depend on one without invalidating on the other. + let inferredAssetEdges = $derived.by(() => { + const writes = new Map>() + const reads = new Map>() const g = graphRes.current - if (!g) return out + if (!g) return { writes, reads } const liveAssetsForPath = (path: string) => liveBodyAssets.scriptPath === path ? liveBodyAssets.assets : inferredAssetsByPath.get(path) for (const r of g.runnables) { @@ -535,25 +535,14 @@ const assets = liveAssetsForPath(r.path) if (!assets) continue const w = extractWrites(assets) - if (w.length > 0) out.set(r.path, w) + if (w.length > 0) writes.set(r.path, w) + const rd = extractReads(assets) + if (rd.length > 0) reads.set(r.path, rd) } - return out - }) - let inferredReadsByPath = $derived.by(() => { - const out = new Map>() - const g = graphRes.current - if (!g) return out - const liveAssetsForPath = (path: string) => - liveBodyAssets.scriptPath === path ? liveBodyAssets.assets : inferredAssetsByPath.get(path) - for (const r of g.runnables) { - if (r.usage_kind !== 'script') continue - const assets = liveAssetsForPath(r.path) - if (!assets) continue - const reads = extractReads(assets) - if (reads.length > 0) out.set(r.path, reads) - } - return out + return { writes, reads } }) + let inferredWritesByPath = $derived(inferredAssetEdges.writes) + let inferredReadsByPath = $derived(inferredAssetEdges.reads) // Same derived shape for `// on kafka` etc. annotations. Live buffer // wins for the open script; everyone else is parsed from the // prefetched body content. @@ -947,13 +936,10 @@ selection = undefined } if (liveAnnotations.scriptPath === path) { - liveAnnotations = { - scriptPath: undefined, - annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } - } + liveAnnotations = EMPTY_LIVE_ANNOTATIONS } if (liveBodyAssets.scriptPath === path) { - liveBodyAssets = { scriptPath: undefined, assets: [] } + liveBodyAssets = EMPTY_LIVE_ASSETS } if (liveContent.scriptPath === path) { liveContent = { scriptPath: undefined, content: '' } @@ -1135,6 +1121,16 @@ // Currently-open draft shape (if any) — fed into the details pane. let activeDraft = $derived(activeDraftPath ? drafts.get(activeDraftPath) : undefined) + // Path of the script currently open in the details pane (draft or + // persisted selection), used wherever run-routing / overlay logic needs + // "the one script the user is editing right now". + let openScriptPath = $derived( + activeDraftPath ?? + (selection?.kind === 'runnable' && selection.runnable_kind === 'script' + ? selection.path + : undefined) + ) + // Entering edit mode with a selection whose path has a draft (e.g. a // deployed script whose unsaved edits were promoted on the switch to // view) re-opens the draft, not the stale deployed version — same @@ -1170,7 +1166,7 @@ // `handleDraftContentChange`, that creates a parent ↔ child feedback // loop ("effect_update_depth_exceeded"). Named functions keep the // prop reference stable so the $effects only re-fire on real - // content changes. + // content changes (e.g. handleContentChange below mutating drafts). function handleAnnotationsChange( scriptPath: string | undefined, annotations: PipelineAnnotations @@ -1365,12 +1361,7 @@ // pane, route through ScriptEditor's Test path — the test panel then // shows logs/result and the user can cancel from there. Same UX as // hitting the Test button directly. - const openPath = - activeDraftPath ?? - (selection?.kind === 'runnable' && selection.runnable_kind === 'script' - ? selection.path - : undefined) - if (openPath === producer.path) { + if (openScriptPath === producer.path) { if (cascade) requestRunCascadeSignal++ else requestRunSignal++ return undefined @@ -1441,11 +1432,7 @@ // drives the read-only pane's run form. Derived from the displayed // graph's triggers so the drafts overlay picks up draft annotations too. let openScriptHasDataUpload = $derived.by(() => { - const path = - activeDraftPath ?? - (selection?.kind === 'runnable' && selection.runnable_kind === 'script' - ? selection.path - : undefined) + const path = openScriptPath if (!path) return false return displayGraph.triggers.some( (t) => @@ -1476,11 +1463,6 @@ // but with no drafts and no live editor buffer. resolveGraph is pure, so // feeding it empty overlays is the cheapest way to share the logic. const EMPTY_DRAFTS: Map = new Map() - const EMPTY_LIVE_ASSETS = { scriptPath: undefined, assets: [] } - const EMPTY_LIVE_ANNOTATIONS = { - scriptPath: undefined, - annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } - } let deployedGraph = $derived.by(() => resolveGraph({ base: graphRes.current ?? EMPTY_GRAPH, @@ -1504,15 +1486,14 @@ $effect(() => { if (mode === 'edit') return untrack(() => { - if (liveAnnotations.scriptPath != undefined || liveBodyAssets.scriptPath != undefined) { - liveAnnotations = { - scriptPath: undefined, - annotations: { inPipeline: false, triggerAssets: [], nativeTriggers: [] } - } - liveBodyAssets = { scriptPath: undefined, assets: [] } - } - if (liveContent.scriptPath != undefined) { - liveContent = { scriptPath: undefined, content: '' } + // Guard before reassigning so an already-empty overlay doesn't + // needlessly invalidate the graph derives every mode toggle. + if ( + liveAnnotations.scriptPath != undefined || + liveBodyAssets.scriptPath != undefined || + liveContent.scriptPath != undefined + ) { + clearLiveOverlays() } }) }) @@ -1685,9 +1666,15 @@ } // Poll a launched cascade job to a terminal state. Modest fixed cadence — // chains are short and the folder poll is already watching the same jobs - // for the canvas animation. + // for the canvas animation. Capped so a never-terminating job can't pin + // `cascadeRunningRoot` forever and wedge every future cascade: after the + // timeout we throw, which the orchestrator surfaces as a chain failure and + // `runDraftAwareCascade`'s finally clears the running-root guard. + const CASCADE_POLL_INTERVAL_MS = 1000 + const CASCADE_JOB_TIMEOUT_MS = 30 * 60 * 1000 async function waitJobTerminal(jobId: string): Promise<'success' | 'failure'> { - while (true) { + const deadline = Date.now() + CASCADE_JOB_TIMEOUT_MS + while (Date.now() < deadline) { try { const r = await JobService.getCompletedJobResultMaybe({ workspace: $workspaceStore!, @@ -1698,8 +1685,11 @@ } catch { // transient — retry on the next tick } - await new Promise((res) => setTimeout(res, 1000)) + await new Promise((res) => setTimeout(res, CASCADE_POLL_INTERVAL_MS)) } + throw new Error( + `Timed out after ${Math.round(CASCADE_JOB_TIMEOUT_MS / 60000)}min waiting for job ${jobId} to finish` + ) } // "Run + downstream" over a chain that includes drafts: the backend // asset-trigger dispatcher only resolves deployed rows, so the page @@ -1806,11 +1796,7 @@ // in `triggers`) other than self. Flows are excluded because V1 dispatch // only fans out to scripts. let editedScriptDownstreamCount = $derived.by(() => { - const editedPath = - activeDraftPath ?? - (selection?.kind === 'runnable' && selection.runnable_kind === 'script' - ? selection.path - : undefined) + const editedPath = openScriptPath if (!editedPath) return 0 const writes = graphWithDraft.edges.filter( (e) => @@ -1841,20 +1827,10 @@ // folder…" entry in the dropdown otherwise. let pickerModalOpen = $state(false) - // Native trigger editors mounted inline so clicking a "missing" - // placeholder opens the matching drawer with `script_path` pre-filled - // — keeps pipeline drafts intact instead of navigating away. Each - // editor's wrapper lazily mounts its Inner only when `open=true`, so - // holding refs to all seven is cheap. - let kafkaEditor: KafkaTriggerEditor | undefined = $state() - let mqttEditor: MqttTriggerEditor | undefined = $state() - let natsEditor: NatsTriggerEditor | undefined = $state() - let postgresEditor: PostgresTriggerEditor | undefined = $state() - let sqsEditor: SqsTriggerEditor | undefined = $state() - let gcpEditor: GcpTriggerEditor | undefined = $state() - let emailEditor: EmailTriggerEditor | undefined = $state() - let scheduleEditor: ScheduleEditor | undefined = $state() - let webhookEditor: WebhookEditor | undefined = $state() + // Native trigger editor drawers live in ; the page + // drives them imperatively. The draft guards stay here because they depend + // on the drafts map. + let triggerEditors: PipelineTriggerEditors | undefined = $state() // Webhooks have no trigger row to create — clicking the node opens a // drawer with the endpoint URLs + the webhook-specific token creation @@ -1868,7 +1844,7 @@ ) return } - webhookEditor?.openDrawer(scriptPath, false) + triggerEditors?.openWebhook(scriptPath) } // Data upload is a UI-first entry point — no trigger row. Clicking the @@ -1910,112 +1886,19 @@ ) return } - switch (kind) { - case 'schedule': - return scheduleEditor?.openNew(false, scriptPath, undefined, scriptPath) - case 'kafka': - return kafkaEditor?.openNew(false, scriptPath) - case 'mqtt': - return mqttEditor?.openNew(false, scriptPath) - case 'nats': - return natsEditor?.openNew(false, scriptPath) - case 'postgres': - return postgresEditor?.openNew(false, scriptPath) - case 'sqs': - return sqsEditor?.openNew(false, scriptPath) - case 'gcp': - return gcpEditor?.openNew(false, scriptPath) - case 'email': - return emailEditor?.openNew(false, scriptPath) - // webhook has no dedicated editor. - default: - return - } + triggerEditors?.openNew(kind, scriptPath) } // Lock the script-picker to the related script so the user can't // reassign the trigger off this pipeline from the canvas. The trigger // can still be edited everywhere else (TriggersPanel, etc.) where the // picker stays editable. - // Delete-trigger confirmation state. Kebab → Delete opens the standard - // ConfirmationModal; the actual delete is dispatched from onConfirmed - // so the dialog stays consistent with the rest of the app. - let triggerDeleteTarget = $state<{ kind: NativeTriggerKind; path: string } | undefined>(undefined) - let triggerDeleteLoading = $state(false) - let triggerDeleteOpen = $derived(triggerDeleteTarget != undefined) - function deleteAttachedTrigger(kind: NativeTriggerKind, triggerPath: string) { - triggerDeleteTarget = { kind, path: triggerPath } - } - - async function confirmDeleteAttachedTrigger() { - if (!triggerDeleteTarget || !$workspaceStore) return - const { kind, path: triggerPath } = triggerDeleteTarget - const workspace = $workspaceStore - triggerDeleteLoading = true - try { - switch (kind) { - case 'schedule': - await ScheduleService.deleteSchedule({ workspace, path: triggerPath }) - break - case 'kafka': - await KafkaTriggerService.deleteKafkaTrigger({ workspace, path: triggerPath }) - break - case 'mqtt': - await MqttTriggerService.deleteMqttTrigger({ workspace, path: triggerPath }) - break - case 'nats': - await NatsTriggerService.deleteNatsTrigger({ workspace, path: triggerPath }) - break - case 'postgres': - await PostgresTriggerService.deletePostgresTrigger({ workspace, path: triggerPath }) - break - case 'sqs': - await SqsTriggerService.deleteSqsTrigger({ workspace, path: triggerPath }) - break - case 'gcp': - await GcpTriggerService.deleteGcpTrigger({ workspace, path: triggerPath }) - break - case 'email': - await EmailTriggerService.deleteEmailTrigger({ workspace, path: triggerPath }) - break - default: - return - } - sendUserToast(`Deleted ${kind} trigger "${triggerPath}"`) - triggerDeleteTarget = undefined - await graphRes.refetch() - } catch (e: any) { - sendUserToast( - `Could not delete ${kind} trigger "${triggerPath}": ${e?.body ?? e?.message ?? String(e)}`, - true - ) - } finally { - triggerDeleteLoading = false - } + triggerEditors?.requestDelete(kind, triggerPath) } function openEditTriggerDrawer(kind: NativeTriggerKind, triggerPath: string, scriptPath: string) { - switch (kind) { - case 'schedule': - return scheduleEditor?.openEdit(triggerPath, false, scriptPath) - case 'kafka': - return kafkaEditor?.openEdit(triggerPath, false, scriptPath) - case 'mqtt': - return mqttEditor?.openEdit(triggerPath, false, scriptPath) - case 'nats': - return natsEditor?.openEdit(triggerPath, false, scriptPath) - case 'postgres': - return postgresEditor?.openEdit(triggerPath, false, scriptPath) - case 'sqs': - return sqsEditor?.openEdit(triggerPath, false, scriptPath) - case 'gcp': - return gcpEditor?.openEdit(triggerPath, false, scriptPath) - case 'email': - return emailEditor?.openEdit(triggerPath, false, scriptPath) - default: - return - } + triggerEditors?.openEdit(kind, triggerPath, scriptPath) } // Reuse the empty AssetGraphResponse so we can still render the canvas @@ -2456,11 +2339,7 @@ // per-node Run button. The currently-edited script // is whichever path is open in the pane (active // draft, or the persisted-script selection). - const openPath = - activeDraftPath ?? - (selection?.kind === 'runnable' && selection.runnable_kind === 'script' - ? selection.path - : undefined) + const openPath = openScriptPath if (running && openPath) { activeRunnable = { kind: 'script', path: openPath } // Mark the tested runnable as launched-from-here so the @@ -2497,14 +2376,7 @@ // via the explicit "Discard" button in the pane. selection = undefined activeDraftPath = undefined - liveAnnotations = { - scriptPath: undefined, - annotations: { - inPipeline: false, - triggerAssets: [], - nativeTriggers: [] - } - } + liveAnnotations = EMPTY_LIVE_ANNOTATIONS }} onHide={() => (panelHidden = true)} onDiscard={() => { @@ -2583,44 +2455,14 @@ -{#if mode === 'edit'} - { - if (!triggerDeleteLoading) triggerDeleteTarget = undefined - }} - > - {#if triggerDeleteTarget} -

- Delete {triggerDeleteTarget.path}? The - // on {triggerDeleteTarget.kind} annotation on the script stays - — the trigger will read as missing on the canvas until you recreate it or remove the annotation. -

- {/if} -
- - - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> - graphRes.refetch()} /> -{/if} - - + + graphRes.refetch()} +/> {#if leaveModalOpen}