From e965fed99c9f117731b72301840ecd6cfa58b1a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 7 Nov 2022 18:13:17 -0800 Subject: [PATCH 01/42] Let's get this party started --- job/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 job/README.md diff --git a/job/README.md b/job/README.md new file mode 100644 index 0000000..a5bf2ca --- /dev/null +++ b/job/README.md @@ -0,0 +1,27 @@ +# UCAN/CACAO Interoperation Spec v0.1.0 + +## Editors + +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) + +## Authors + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). + +# 0 Abstract + + +# 1 Motivation + + + +# 2 IPLD + + +# 3 Acknowledgments + + + + From 2ea97a6cba4d1c75dfcd75576a2a72955bad44a6 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 7 Nov 2022 18:14:29 -0800 Subject: [PATCH 02/42] Typo --- job/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/README.md b/job/README.md index a5bf2ca..1fce2f9 100644 --- a/job/README.md +++ b/job/README.md @@ -1,4 +1,4 @@ -# UCAN/CACAO Interoperation Spec v0.1.0 +# IPVM Job Spec ## Editors From a2b337f69ad7e3b86bcc6af0d7c366ccc2e8ea59 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 7 Nov 2022 19:23:44 -0800 Subject: [PATCH 03/42] WIP -- mocking out the format * Trying to incorporate warpforge formulas, bucket-vm, and Bacalhau jobs --- job/README.md | 38 ++++++++++++++++++++++++++++++++++++++ job/job.dot | 9 +++++++++ job/job.png | Bin 0 -> 26915 bytes 3 files changed, 47 insertions(+) create mode 100644 job/job.dot create mode 100644 job/job.png diff --git a/job/README.md b/job/README.md index 1fce2f9..08c1a6f 100644 --- a/job/README.md +++ b/job/README.md @@ -25,3 +25,41 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S + +``` js +{ + type: "ipvm/job", + version: "0.0.1", + requestorDid: "did:key:zAlice", + config: { + run: "asap", + maxGas: 4096, + label: "fission/run_the_reports", + authz: [ucan1, ucan2], + visibility: "public", + verification: { + method: "ipvm/optimistic/zk", + min: 1, + replication: 2, + referee: "ipns://abcdef" + } + }, + interfaces: { + // ... + }, + invocation: { + type: "ipvm/wasm", + executable: "Qm123456", + inputs: [ + { x: "Qm123456" }, + { y: "Qmabcdef" }, + { z: "QmFooBar" }, + { + database: "dnslink://example.com", + effect: "dnslink/resolve" + } + ], + }, + signature: "abcdef" +} +``` diff --git a/job/job.dot b/job/job.dot new file mode 100644 index 0000000..98d5951 --- /dev/null +++ b/job/job.dot @@ -0,0 +1,9 @@ +digraph G { + Invocation [shape = box] + Job [shape = box] + + Job -> {Invocation, Config, Version} + + Invocation -> Wasm [label = "exec"] + Invocation -> {x, y, z} [label = "arg"] +} diff --git a/job/job.png b/job/job.png new file mode 100644 index 0000000000000000000000000000000000000000..beb828efb5570dda0cc1ecf12388a08ec262e09b GIT binary patch literal 26915 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 literal 0 HcmV?d00001 From 0eed3f8e24b77a1f20069ecdeff95cd6c72ce0a8 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 7 Nov 2022 23:42:50 -0800 Subject: [PATCH 04/42] Iterating on lots of small details, trying to cover Bacalhau --- job/README.md | 44 +++++------------------------------- job/example.job.json | 52 +++++++++++++++++++++++++++++++++++++++++++ job/job.dot | 38 +++++++++++++++++++++++++++---- job/job.png | Bin 26915 -> 111032 bytes 4 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 job/example.job.json diff --git a/job/README.md b/job/README.md index 08c1a6f..cfbf8f3 100644 --- a/job/README.md +++ b/job/README.md @@ -23,43 +23,9 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 3 Acknowledgments +# 4 Prior Art - - -``` js -{ - type: "ipvm/job", - version: "0.0.1", - requestorDid: "did:key:zAlice", - config: { - run: "asap", - maxGas: 4096, - label: "fission/run_the_reports", - authz: [ucan1, ucan2], - visibility: "public", - verification: { - method: "ipvm/optimistic/zk", - min: 1, - replication: 2, - referee: "ipns://abcdef" - } - }, - interfaces: { - // ... - }, - invocation: { - type: "ipvm/wasm", - executable: "Qm123456", - inputs: [ - { x: "Qm123456" }, - { y: "Qmabcdef" }, - { z: "QmFooBar" }, - { - database: "dnslink://example.com", - effect: "dnslink/resolve" - } - ], - }, - signature: "abcdef" -} -``` +* [Docker Job Controller](https://kubernetes.io/docs/concepts/workloads/controllers/job/) +* BucketVM (UCAN Invocation) +* [WarpForge "Formula" v1](https://github.com/warpfork/warpforge/blob/master/examples/110-formula-usage/example-formula-exec.md) +* [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) diff --git a/job/example.job.json b/job/example.job.json new file mode 100644 index 0000000..f4aa8dd --- /dev/null +++ b/job/example.job.json @@ -0,0 +1,52 @@ +{ + "type": "ipvm/job", + "version": "0.0.1", + "requestor": "did:key:zAlice", + "nonce": "ABCDEF", + "config": { + "label": "fission/run_the_reports", + "start": "asap", + "timeoutAt": 1667878395, + "maxGas": 4096, + "authz": ["QmUcan1", "QmUcan2"], // TODO move to post-negotiated? + "visibility": "public", + "verification": { + "method": "ipvm/optimistic-zk", + "min": 1, + "replication": 2, + "referee": "ipns://abcdefghi" + } + }, + "run": { + "wasm": "bafyWasm", + "input": [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "with": "dnslink://example.com", "run": "dnslink/resolve" } } + { "z": "QmFooBar" }, + ], + "affinities": [], // TODO lives here, or in + }, + "before": { + "database": { + "with": "dnslink://example.com", + "run": "dnslink/resolve" + } + }, + "after": { + "effects": { + "bell": { + "run": "system/bell", + "on": "always" + } + } + }, + "signature": "abcdef" +} + +{ + "type": "ipvm/deal", + "version": "0.0.1", + "accepter": "", + "job": "Qmabcdef" +} diff --git a/job/job.dot b/job/job.dot index 98d5951..3d899cf 100644 --- a/job/job.dot +++ b/job/job.dot @@ -1,9 +1,39 @@ digraph G { - Invocation [shape = box] - Job [shape = box] + Pipeline -> {Job, JobL, JobR, JobE} - Job -> {Invocation, Config, Version} + // + + Job [shape = box] + Job -> {Invocation, Config, Version, requestorDid} + Invocation [shape = box] Invocation -> Wasm [label = "exec"] - Invocation -> {x, y, z} [label = "arg"] + Invocation -> {a, b, c} [label = "arg"] + + // + + JobL [shape = box] + JobL -> {InvocationL, ConfigL, VersionL, requestorDidL} + + InvocationL [shape = box] + InvocationL -> WasmL [label = "exec"] + InvocationL -> {d, e, f} [label = "arg"] + + // + + JobR [shape = box] + JobR -> {InvocationR, ConfigR, VersionR, requestorDidR} + + InvocationR [shape = box] + InvocationR -> WasmR [label = "exec"] + InvocationR -> {g, h, i} [label = "arg"] + + // + + JobR [shape = box] + JobR -> {InvocationR, ConfigR, VersionR, requestorDidR} + + InvocationR [shape = box] + InvocationR -> WasmR [label = "exec"] + InvocationR -> {g, h, i} [label = "arg"] } diff --git a/job/job.png b/job/job.png index beb828efb5570dda0cc1ecf12388a08ec262e09b..7ded09f7011639cbf8180246231ce9765e7397e2 100644 GIT binary patch literal 111032 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 pMzdxtsK(~#>wtRw7oPTsM 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

2UxF5BVQVNcOWbL%QQ-QBG715ADh2#S;lxKmW8| zi!7`m+43HYmz~t&o7R{?iK1j1h!ksh+-7bOgxdiEt-N$x0x;Jwh&@VKBEgB09I#rJ zY5|V$T|mC|(BH#0%T7R`?94wpBSy#qR}TJUuuE{477RI*&2Ernw;=P*vU8pX#MTi# z8cwc_IBj%!d%8EWd49QvSmom#9bA(UV$u>|W@~t7ZSIvMB5s0htKUGgH=OELd(8SCf{ff_*qnY+ur@BQ1{6=K7Gm(OrQd(dTv@}8cRdZnd1jZn@gX0 z4i*MXx+lJWb!ZI$5Trl~?vk>0qVF78P)IRsdUa`I2D(vOh~XlEFjD^;~!ACgSNDz zI9y-5w!Jvh2(X+?AiOdT8)rvvkr`Ssv30;AiZ(x>uo0Z77@l)GCQ6O@@#Dp7hbQod z0R)YP`{+N`0yzp?7(EO{+yPF+c~Cs#^KH){$Rl9LmS;MLNoXigN8@bKfOa=iV@tz^ z4UIzPef7>6#h48$HYrPqplnJ#B9scVH2*b#lE+n3{U<*2!mX4~_7%g~v_c9Qi~g%D z#TlrQ&$u5+hQ3>^$a&~_M2SPVsd_nnF3lOw9uZIQ!-6R8;|sZu<1sXlu?9in7@lK3 zYEJWAo-?-FWCXg|-kpbRWkOSG#^R`F^BUh=aLM+Mgf65>aHNyr1O8h5>FK=% zu2xvgcqwUyw~!#;}V9G|f6 zJbv!qK7_+i5L6GcQko3w8C`>1ifx$YA6-{hZNFdS75d-}L+sqjlvQbscPW^TBcKEt z%|CcxU3H~BIR85^V|*)iP*9*^!2E?tk-kJJU)IV)rSv(=dMaMQb)A}?y1L>HC$73; z$$>$JUJx~PLDTthl#{3!k0dF4s~l|1@d*6sNFW0 zeQ_W3D2lS@jj#p05vnE<9dRMMQ<9fz*BwV@yFh7|&IGs`@?SndBjHJ*Jn7nOs4f|A z8Owm+(l*f-t(Px<#_0@#=I+&~E>eSMgyR)-wd|c=_%5HZ!jKx>;lod$xSLLVzX18s zMsYPN${%9r3HP~w%&LVY*DAEs)XsrmHyIGEoexpvLx`MAa#cM!WAd)|V z=oGPwg&Sd|Ra;Q%y5p$z1NiHw=z_VQg=MwL1SZ1)1*U0v{OHY1Utl14sC(}ID6x<+PB$i#rk$n|?ZtlzTt zc-Y~b-nCi!iN36`JXR%+}Slm0}1+P9M4KX zR1CpDrm4EMQUr4}gwrL{3dB}Im*Ha3Gsl>S&U8B9&1oCZ6Rt!5sOPlQ`VeV;B&>)Q z#^HPS?K7mfGWz-sSa@gI@={Zx0>HnC{aW|zZZywtdQ#BwetIims4{L(G$m1iP<{u5q7&E-v3Q#*SYgFJZLg5OlwJb^sW41w817 z;^NtGSD_QPE$6ZXPzF3h?D_y>)}ianY4(-IY~AP;-I>jJQ*>*qB3_oR;X%uwyT(PD zsAnLEfxvXV+8lh4lXG@nsOgvnP{CUN_EQ<>Er z$L03JBZ22A|0exErvr|TlQ+> z*1Zv*)dn!Z_N)KexQczS1&@Ygk<2Q;M5GyI?}jK$hZXxb0Vz^|-;lv`sAcAw$88%) zV|cXw_;Ye=zy>}8{hv~sm4~Wm0$Qb|YuCJtdMwkq^ ziWDkt$e8d#I0bufZ{zoIbE%)kqupVgt549TcLMQN(~s*!>0CRHH`(9!&2wA2YSj#A zsB?tVti@i2+7n-+SfBx10e|APE*W1UVhs%8|OMMW8G5v!A?n`CUq$%ur0 zmY{hsVF~n@AEbIZM6y0T;9`}tlL@j<$TgqAgXhhhcm4YH0C_PyE3^Fp!}Y)QEbmRea&gm}J4ACa{<7F)du zKY#q_L1^Uy`VjyJdx!nLMp~H-dC?m=3jk%iHf`?eTv-3EVtEMbwcv2{41+wEYIl}r zOzph!c53cT?UP499mb(53eFg%Q+0x4O))8CSRi1boF#mA+jZ(>8nk9Ag5bFrf+RnG z{x5?sI;6V3HM`_IY zenBtCz?Fnfe0IxyK-g$&?@R^5(vqA8XPf{FcwTqOxEI=imQq*PZGNC~JW5 zuCc=Y+v}5vI%YER9(r%<0Kz#%ksk!+F@!Y`0pF*s1t4udbPi)NdW=r>Aa1W;x9%JT zdQrlO%0Cpwz99QIEfWL@vT><`|{-l`q78)MPsvmj_Vct zSj&F>`tzV#3j-eaa5Mp)8Un3ccN~k8pQN^rpz<3cbVO%&;y~!ctt&^@uJZD#r8bGJ z^v){aYG}feen4+S&13l0qLlOZb5q)M)Sfo*(Q@RMEn7AO9DV!Q&(q7R>sKch&DI5? zlrurhS-){(M#Y`akqgOa~-Nbhf_!tqCf0R?m&MKURHMJ+0&!M$4j`i7hRi1z&aDIW?8_0daitSYz5dgpntz-Iud;n@Htoi0)p!H5oV z_`XOgjiO^ElaY1F^We4sv!?^ho9!=|7sm=02@`O;`cav4Nvuvt_W0dohr{if{8Mx+ z<^F%3wDD?~M#-A)`u?b{C*dK#M8(~L<*4Urf#t?dQ}=q>(Q=#b=23NHwFp#eV`@sW zfm8w!Gt0ktixu)k>(n#oza9`grGDv{G;SSQi_4q4sUaYVTzs#Ad-Bo`n2L5Ot+m5Y z8`)1+lG5S70=_fay?gaK2ELjJ(TYGbK}XqzUh{&~XjEbhy&xHEo~Ap0bggp0C<%=8 zNWI*qK6vxSo94rG|Ap%{)n%5v`EF?L=%GE`-HBY<9OzRhhzZS=H2Y@z{RTmebmxUo z8Ds|eb^hOJ7{G8bQWJfJ4P?)@kis%tPV|uUs8X%-+x*Dv9`;ks@BE3VbjkLq`B_%h zmqd$Pa-9E|Q93$0r?~f%G1o3(7%uA_-+RTg=;odsi2dl{r>eR=p=2mob9ne_%s9?5 z9eAvJBbpoatapLaX3V%i7ic;iA%)S^GxJ@x_^8D z5#mSHh6S0Mh&fjyBjvX~<@uWKpBn_^BLSOSB)x&!m@(e=QCr!Y`!RChymOUqsD_zd*#%^bU-hvS%A;#X*J^yyuNnEx8fC1cC47g@FJgOG6uUj7IIcK!Nz~(ds;wD6-oHXw11rko zxL>RwsymA}|%#XoTxlhwAQ~ zFrK!uFBnhCqep0v+sdy4e6ReOs%5qxiDtlxwO!S`i4A-xLmQjGVReHeB_GgdhKGk| zrMezDB7qwfVfNM+w|WsD*fwB8VQD_#L_zI8XPvK^=KJL7vpnrEZ{{=2bLhmN_3RST zN3|`vDy(>ouaUZlSPA9k{>xSSlS+~41TubPu5$Tfh4W5=`aC(Nl#RT(gFM6qy-kjO zK7_+50ip!4Hgfye?=@eVR=)JfFD=_OHY*pA_*Ctap((|oH~7}XiP;Wt(N`U-$9;wd zp4zJZ%XsJbYjuM&q}1V~kZA)l*TZyM*`l%V*Iie&8s(x3FQys+sd;~of?8*y;1V}y z*PfM@1&6|WSr?#GqwQOPhSYq(W@;yjxKD^2%m{6Uob9C%)&SCY@v6+@zx9XV9pZ|=b zpQ_W&`}@27-3-BCPIm2=#CutMy1uHaXAk_KsOu*&v-q9o;92UvxeXQ|pkkT1VPRqE zm{Q5cF=3UHToA(|q^YNX+F8dadFBR+iG3a(&m5EP>XY_}0FY}+;!0)SZ!iOgVC-UW zFJzgQw*AQozB|Y~XS}_yIM;4>IgzAY3--;>Tz>(Z7)-E<`eG8KUy(iOAfy}OiyH10a?3tWeO{*>})R|G6TY#iKvoX7Uc;zg> zzb6bfV(d6KFF!V!$qO+s{qiQdBS_keB_dvBGK} zh$fxgA3b{HPuOmwD17RF*w(p>fjh9>R&~0!YhX4LRSox&!}|jG*uzA zuz9~UT?OR+fXA#xG}TBwNHqBS(xF*ss<&Sii>JYuK+e1mDs? zo0w!bscn&6wn;2`sbktGbb#No9>;cO*x}}l8#eh7KBKp|*B}4nq*)%y5y@>}OlHmK zG{E!pP>kNFCPNq3bx2&IZt3O| ze=)z9JnYU~#-o#i16XORudn)_p3e3tL6LgeziZ#Ec5zr6P2Zb%=M=q)`~)SfzN1E+ z2Ao*|c&SkA-LH6EY)Y796@2BcC{5=`+Vg$)Z=lm<9b z4Kbsk#%edee4}1DJ_An2l`OaSt-(>@{Dyk|m2;fQV|}S$^ruc;q;hE1NQRpM{bFyq zA}5I5J@fq4tdkA?`G+f92OV?JlhP3CtZ_&j*5NCHVBNITfnNbLvXi5LCVS@BT)EUd zppYjcOiyDUjbhiVFk)ULAPLWF#kT&sPF7g+_F(ICZmy+Y{-+5w{)YV`u3Gx5=UaAM z(Z5!?p@O(_AMNb{;1b&p)upiu!DAF8*|6g;sE@=boIjCaGjjb+kpVJdc42br7L!}w zV@F`3%)r6I)lCx)T@3lp(?hRo_-g%ZxK8TfX&Nsd2&aHqE@f+5n(@r%n;orf%Br#f zLKNpFxH@k22A%Pvc7Gn{6g@dLXljh^{9#U?(+Ve4f1VIs7GL69!_ZMWmIy9unqE2z zEzywN8K%-(0`zL@7M>02*FOid5Qx*v-i~OVY+4xvRe$O-)V4mv2e?h}m>Lwz!9z8K zp~YB#F|W$DV0FwFf=VLJEnIgd8CT1*@=m=X%64v!MGN-P_UwU7ei#S zz!tpqP~`*vl5cOiAX^5`O*id)`*6fKWfc`h*+bIT=X_XQIO7#=Uh9ytL-(x+ZMN`1 zeD_AJ>esn_IJXGNcpinv!}t9RjDNNiM{2yVMFJ(_BN2YNG<-6vW~3%VugqeIq1F)d z7HdsnqG5h|z|K-UJT>i1@AY5roby+-b0#Ab5oa3t*-QjM$Ii&B^Ve+v1sC*$QvZo2 z((mu3g|ebs5j9-K<8&*|fG%Ssa2`pB6NRiMp$yh-JEIGwKnI-C>l3bV836C=8U}b(n|5`g(-We(@aqt?Q`MtpQN=DRo!IwQ%rLxJAdPk<3wRE z%4C;zBY+$r{{n}8In1t;4jmeBKuK06r4CRrAe;M0tBSw-z(Cj*bE#!gL#7;=6by`w zhZ{KyF9m_X7QHr>+kX7BqvPOqIB6c?>BF_p1BVIs*=?+{stJQ1b+ zke?MhJ9^hay$?BRVP<$CN03DdFkb^3IRQx_Mq80|pbUthr}(b{x2j!;d&Nq7xQ^ z99xCdAs2()Yr($#IIpyo_kq%{(2xo_%~_a{_jEEZAAS zETp@Y(R9dT7q1_E=td-c3i}rxSJ{NpO~xQ29P}m0!kDnYFw?a2tz)$i_(*gc&_Uco z#UV3%G=2K^6TQ8H-JRbiG#`Mf2I&jrvoNg8%&nO5_v+$|F%9ee*?I9+UO{jCjMRU; z*_>NPF}AwoS=r-9r$zmZm6JpHci+U+;6}He_+SH%v-@DKiI^}5Cq$%#7Hj=1d0gca zpUl0L{bkMqu@$2Sx7pnVQ@2d%sAje$o}n1G$#_bZ?^_u2|IM;e07SHCsE~iakK1kE zNwT@Y$8jrA(6HMt(5)_H_dp2Q4}f)Cm=%=8k}}3GLIiG# z6;gqJF~B9}9t6^N`Jhnnc@EsTF~TKQSAky++8PT#>zL%zg-6;Wq)=$BztHd2G3U3F zCa%RPLl(Ee&pi<-^^1sx%=0{460BZP%i_&+GRus!qdT+Gx;(!2k1mgDpru)u3q3RY zB2Wo7meX+j-mCV-v$!#wcxFEdL_p9|R@s`=>%c&ff^tgsNbv%gbv={Vr*2$-5}V7K zF~LnMe3%Ir;vUXf7IO0zfNnfzHJfL$x2oPaIPkZuI_E>GXw$pYFZuWENl*9Id?5(QN@8Nz zcy+J$R38n$FWKx)J{WX(dbi4mTXURygBh?$U@sF1SS@-~bjY~1z`6I=whIHJ!Dm-g z-SFHDvzPQrfE;uG2KuN@8SUE%*kK#`gw$M5!BlKRg_w|{2wiB~N6OMsZam$!a-6O7CNw?{|_UA<%s|2jwQNBQV|Gi(%3dSfV@hB7u{;mwnDF(i| z<){9kmXFq`Su>-e5>93}-cP5|4g(`@D87F1$%E;P%)0a8>!Hmiki|?%to%}KX1N;& z&V2;x6H1-zH*H$h&P@kvSp8}h?&Sfrp$yvp-NlabQ@)xE7iei6HCxbI-Lb~r`|lg# zCxxu3S$S&gbw^4c#A;Hc6z<~;d%ePmv^^|<9H5=7^wxQ>a z>mh^Z4O!TbPQMs`MJv8T@yJ4jBtFqmAd$CFNn)!JaYNtecU6&2PJ?&q zJ#)TQ_+&>p%JdE$2%o>6gjkk=MBWOX;d4p%*7t?)Dc7LiWT%D#KZ*mS_4D+TGxF+g zjMt<%Oi}gHw?9Aod`PKD8;}1!RL>8SIu@Y}`{v|z>%CH^PPcKg_H!{tivVKB>fi=E zEu+;9dwL}Qo##O=Bl2$qO&K+B5D&TVrkhLSMA}MO$vy1UJywI1Q6V zR1N;=+Q11$5x%BdPU~2)dao@s1*iExdE+W-LfP5~HodIFMB@kX@E(e{6dC?!t;X!0 zD}@3b1m4AwMIxD@9YSOC>|RgSv+q0bh6Vo-s8 z;XePAv(5_xK38o}Zc3ape>L*FcBCVi;uY3?f+k5TXWrRUO!^T7PJX4^ep<)4=`<=e zEkR5Zqhm3m*U;MF2sCGIezLC;dpFE_u@+sQ24Q;f#(RB{eE8GcF%Bz*Gjk=$igol_oC@PO@Axr{LqqC7=R` zT@q=UWCbI$7J=8!ruQcIDdoCfCPK})Ug5~|^Y5ia$^OK&bLg*Hb*?)ff8HzYX|IAO z)Sv_I(dMAI5bT}cyLB}BKCr@v@9Rc-K&vwgg#v&zEtkB0mP9T55nQb2Zy0pt*KX)O z3RiB#Ik&Qg77KPWvd~2SiMB<+(pT|UAMaV#{#h!KWJrz51MOPQ)m~GLM8+!-b6(gw z8hs$`jCGr5ssATDFy&`yzbtUrrYQ6;s?>GQzKH!MZ3=vo#%BdoNe-XeYMy93t^4pEB9TOtN$=XS?w#+4t49* z8*09eX!ry?e&e^-W~*;sCIpe1hK((MTQ6$NJGAe06o^4~GNc81VaHxh8eBG;wE-xg zewC4KcyxsVxLpqd*dWY&s6y5S?&zqpqfPJmvtqUlV_ZwC^wuF<%Jw2y^5uIfZK;)6 z1~C(zoQlU<(BOHXU`+s0QCHc9Hp{Be0vrw4)b%-Sy!fd=fb2G#Vceq@{vZ+WR~fD-CphHw{vcOf&EJ{z|1k}kCe1mKP>Z@kosZlylu**SD! z4j|=l3RA@6c6FbFINWTu$-hwkPx<`Q$J8Wrw0wfbYWkS5SOY->86j49$xNES?m){< z75OLh^mvhzFo4UTkPwDMC~ska_|HZp?zXtA)j7wLNvD&Xh`#Is)k1595~G=KVjWK~ znt%>sx8ez9Vn0>Y^LKU2Ab>;}gUkNj__9>d@9@#|^$*d!dJoU++#hNt15<97%u~m(h?0d0f`DN8qLf;E8lR6{3Y>(lCZ;^uTSZiQ%9%CN<$rvn z@kr7*+s3$et)3n(NRgYq)~+nhxp!XU*YF*=gPT26)VtUH(7AKF&MOZ|pSNy|@%wqp zPA~hm?6kMl(3zW4AOA_+l>PAQWrJ(xS!-S_o?oc{$W{HI!6kV1M@XpYaYpWm^iIYG=h-%vLM_m&d;>bcjda%8nEE z;#yNh$TKZvVBZWQ&kanj)W6N6-p) zBU&-vcRf|cM!ipJ+AV{ZSy_pQmo-f<-wyMeA2tu&`S#-4`5GOD4Il1PT8!cT<=EIa zrMbgSKPp`r@GSY`$7RD<-FfcZ+tBc@%?SWo9u3XD;dJKW#XkG?>{$m{vZh;+bA{!G zi{M{P*}Gm&l^<=dy!NwCFQ$eb3#%9uG|k>~%?oF|zBhk;X`dKh&vq7eZd zrxzK${=6fvW+tLsnk*5)2iUzmoEOjD$t@0R#TgL>38gHc8!fwyk^J2 zl*H(-*Iv_2c!1wDBR;cP8F$x(t#ZZFUlg{>|9q;SPmC0j=7#+ar^n zU#+J7|IclXFI$+(pZV16nbOTk)*Kk9OnF;US-^ONkhop9eIK7`_xN;5=_*`K36!!K zljwxWTV0-*jB(OtT(L=4J-Ym7RB7m$qa!@RANp8?C!xb|Eja7=%`wgR+a)TjACMk- zm!iKJs<46gb%FD0zmFe2%<)VAo?BaUJz&$C=tzK^XRC%eR-H6O-z3W$)3)|rv^j!F z&dNW3eu0Q}?J~Erc3(;O@k8>yDet^_t#FsYejDdQCbN060OoqM&bLv;F%!e)rIHo5 zZd|aIE#S$l#~S70E`ImO}-k zVG4N~5#Nwb&6HVOi*-!kmq88UZk+Y{)jIdPe?`QLRbSHm_Ro%M`$h#&>tA@vfM-EE zhgwazk=inM>-A?~Av68b#R$_ubdve*nQmM=VWTjarCY~f7ZakRe?0;)a^K#)cF>+(Enn++&tgOB zDdtI*V!1md>35%50Ujm`?c3+Qe?R*7ms^kR{GC5r4mGZB>Z?C-Vm%KJkCD&-UB_-S zwLi0_2#<&h^Fm51nwG{`6DfD!H_493(RL25cwaS!r;rg#$z1ot{-sCbM)mXdCqr)q48VP##+0)`+35U=i;F%r$Mv8Xehjk+yURa;7 z1J0m{C@C+08ebCbUdS7q?c5cG?P-SFw*C2f-8kvf+t|Ow{@tMBzOw@IIOmsi z9{wcg1DVOQ3%QmenI z(LeR8^zo?TCyNGt2kWs&5LTL@Ne%@^q@vuYDS?AN;4| zc@O@~n6lS$>ZWwvvS(BDLi-Dv16VF9Q}OTib@9yoRuq+O;WyduvvyyNv?SyVov`qR z9kmiKS332I!n}3nNk#y+6~~r&emqv=@7zAd-?>NJ zYN91KAm)SnbO%7POuJiWs3q5P1WSJX%0uUQscPN|Z;2IhQ~i#PHB?EkVp@C_Cm)Eh zpHIWjmX&2g))w;8%;(SFlD%+qYen&`BF$@&k%6um*7@u{U2|RNS~A&OqOtovkrH61#k+!Xb(oRYbRq@p`l@ zkA@^L)3$;<8sfU4!Qq6v)O+iZ%q*pv zA#kA!FIIg7vx<4Y>-;6Wvrcm~j^Ot%^M)gquha34As^Wc>~t$()JhcfojY_$>>nQE z{`KqE;Jm4|hxkbw*D?v@`(o9y1q)jKXd16jv`U|*H{M8@4L=ikfbBvuQwVr-Lo(OH z_(r~172NZtOXzZ^(+_=$Ar4jSbyU`p^E90`tAL(o%odHiUu9#5ieW2#}>6!^ZO2nH($}vBnP0IcI(9 z0P3l#?D!ggoyVOoI7eM@?h!L(?%cKnxT!c@T?`1=0M;4Yvm%svGz;wOGHiy#y>~h%E0EX*yXFmB8(%$)__X}tzrJ5yTwdO@I;3}X)AU&5dGme_kFRK6 z2bMc%y=MM$7GUPFl*DG(>CSE2J{zDpp@;RDqN1fW+p>9S_o1UeNl!A$7MyoO?mjT# zLv~m(kP{o6hrw!J@7l>L0#NZ;Y)qd-Xxdi(ZvTsnjEy~ZSDaz(3?-Wf>45S)51o+F zl*<3FtFw>G@ovNT9hn-M#)haCp3IQQltxVw(jrWWlC;o!dRHRpiAQC$yw@#+YH3N& ztGD)mb5h1NosW2(o_a~pv^WqbKRO-5}-+7&{$9Wu5sDo$0SE(}i6Rp+%{&n#K z1AChN%;Cr5Ro)PdFp8DL(>r-XYkvZ{ItFky=8&5COhp=f0wvr2 z!TB{Fo89#^`VXg!VhnU;t z2uyXY&v@w_GDcu&`!HSA?EIi}{U!M5ksOq+gk~?bTv@1SkW%S>E zT+m_7*yAZd$}HCW8Y|wNDYDv*%pZ1_8gxg1;6%A9ud+7mNf6DfP=R(YcCd$aqe;)u zl#umy#EHQ4nwFq#$C^ z*yB-MJZe?x+nI+AANnvJcXV*jW{n%Nv-YmeM=O(KWVKr+$+2&o$)?kfs*4mfR=f!q zoe+!v@9KW|J`NuXdsF?Osh4io=lxdk#~%Wu;fUiaanu1O^*mkOi|*pUAu7iBqY2WW zI}@biE_gMVpH4^+afUA{xryc^emH)8!wG7143f0C-t5Q~q$Yz~#fF;~VV6Ijy z(BIH!YFFH3osqh=tlWGfhca(nP-DVDSRBskAE^`|4q+}BEIrK40ALLA61_Eww^YcA zd%85k3ohrqYjGz@Wd<@<*R}eT{+$3Xy)i-cF_t5hl`pe&cMhP7&v=cUOB=7a{Nj7w zYI4w{kb$snFN`i`SKf?0-EJeL`1`aMmLTg8py=N6{k>r?%$Y;6VdFq2W5_4{Ax6CdQqCv2CK;0p zhQ)=ioho)X0W4a4jmDsFVvRRW-UH{$n=AgSaNr9KlMq@Y$4$UZ@|tG?GL*&+>pO_K zaRKM*YOc+B;CWY|yuhRi(qhH!BxC4vZ#t;=*JGvgaj#sbZ_7=FahTr9SVH$TXUMMj1=Ol!ybIy;*e%_kx`=(!g&&LYdDCT6H@nUE;0PB zCGDD;m+Gm#qywp!IzByml=mU~5lPk!3Q`@+lJL{8e94@8pVHMZ7BbwP1MJ6?z`sW; zYZpV3*3Tzw_~=oi2||mf<`nYx7mR@ynkL#)5Rh3Fn&_x~Rf9 zfu{ZH?q@10R*Ka#{J0}9VG4sMF{Q~57+eVd%wYol?|qfJt>yDJ{{tV6WN0iu&JqzU zeBT{d$bpStYRziC$TGd!tj)$gvt=7MZ4w)M0fcrD1LgC9?bHUg<)AZ^-Q;gD@1q+M zr^F0RJ|Eycov|a7JW1-r#W(q@c{0U2Zk`OV-TS zL3AnT$CyZl(E87(gc^Hm6w2x45#R_}t1nzbhi5km1K(yb4py zOjj3ojlNj&0C0|=I243%*Go#~Z`w5Rn7LrfpGJy8D|9UVW?o^2krt{WBui~= zJW`jz5^cMDi5Rvcj3DZM>ScjD1clN)VcRr6{n)c9Df7`kK-R?ru@mS8hmzq4zAcO8 z%lk)~gbFHyj)QrHq??`;Y{hfm`k_Fyj>R|u7+^DM2M=9zs4*S3ON9If%g#b@b7bUI zuV9<(2T^oJgsLU>A>^&atd_lKpHxVLBfZuhiUJHMUrVb@3$A z0{?OoH0ct%>4(M2HkSm>CMy_t!DpZxAz*u7HDP#*?{_XUN8|4hYguY*8`aUV8J9C$ ztjwan#OEc)$ti?L1zoleW_RX{P_8q~4n3EYk@1C~nxwm*=J~{gH%KgdLtRN4Gmkr> zF8e1-cN){DcU-c~MqY~MBkX-ftgnHl4F$q-f-44Ib@BQ6-#w_n9w#NAgA4rZQ3Ink z%|2go>omWF2r1N{xvBPY%e8etVJI4)hFM@_w4*u5lo>r;O66h0LLuZoRPt<4hkF+7 zLZ9be_(S0Q7o$Ry)D0p?zAMxAR=JpPX`)G_-@DICD{dS`t!8J#(S`POG_6pW5OY*) za^A@@JMhwC3c#djn)jbep=0@qDG~`%Lr*n)1SsVhFIu$QxfOxo5Q`0(`~r!&bh4|Pn+ZiJ>3*HJzD+7Si0YM^E-ZQq55?Z`svc?h>({=5F{N)wzYuVa zuu1Y$i6FZtzrg?mh4J|W{LMEHc6aG?B@r=zx2Y@9K+k3`^h^D8)BnbWLYqs%v)n?4HwzNd9?X=ES-NR-$UQg9-)~@9lJuWmg-R~1QLi81) zyE<)fhHq%{;7r%rQ4nGGJBsW*-vuK*$$)VkmtWbeJbbu(j_qE$*>uHEW@e5;gyC#b zk`Vrre2@JxyBE%Z^j0Y9vIZ$XU_jzq?XaO&Mg)|aG=7$toIEceVAkX>_c-58=l$Z= zHoCaTzbFammMw8OQsM{=6x-+hurFeav9PoeBf^P=G3v_>Le(*m0!c#Wk;aHH=Jo5x zi5ng0Ko?0o00jjBx#DV-VWuY|E-Wf4o_nwKtG?hA$oJwgTC4wj-ebW-iHcIHe4OrO z@u`G%w)V)u5_9*RbTA5BW@gW{f8{Caq@QY&U@?cflf&vTD6B3Gi z+Qof5_x*0WyO!BNbLobz*Pl2yl(JIty_s3WnxNJKhdIqP6v_w7odjXI1yPfVcZH?p z$@q9VOm*{Co7Vf<-*W$oik_+kuW-^db@WsHlm}_-)SJ}y@ar#-v_WX%KmY6+G7A<)Tw3tZQ>?s~6_%CPNMn92vM(kox%RLfG)U;HnBXYF>8++0Ear zcN8)+$kOo2AB>M@#y|a5v)JGtF@fiMBq_Z8UpF%_xc>Qbv(TtY?DXl=w?|q7$JylX z(Zok{@Ve9Q(|&i%z<{?)xWBD7T(alauWDP`D2abRlaaI?TW#d)Y(8h!nSZ#eh%3tV z|Mdm0qLgWP-9?$y8`tx?KRW65;0T_mLGirGE<70fdVwMQLy`aZiEuog+4ldlzsyod z%^>UjF5h{H{q?zslO`rRY$Jk#m`(<8(e2xpVkVu`duvxzR20LuZF@)K-4$0m zsqJzX)&Bfk>_dpK5yR%qtLf?KXXoZh>g#_Otl^|IeErJE!Qs2*fNmAf9^N4GTn+JJ z*P3am*KJx&q1|?fHbpx#vE^@ASeQQ6q}q*#4>s@ zBt%{ta({I0qL$Xy(hnaPxw!lfS+wcTPV@{mCNO7T=3zL-!$6_9-M^0?ZMt>q)|oJg zz_cp&d}i{9jzLPNPYXNF-q^f(bN9k{(K;jApFe+2%*>=sOH{BuJD}&^l%&zuU2;D} z)Qm|=O3F^@Y*n5L7S%E6%ID{6jEs!VJQrP$ohvT&*eHCxRVXJfudn*?u8zJxfBtlj zjQ9?>r1jNDE8Mt!yDU=9i-DE3!pL6MTmkRhVs4pPxeQrbo8A%kpmGkSO*$8zE`CKj`se0|SG5 zd(`5(fA>|IetWYc%c`rk#engTGC2)?_&M`S85jSnbuUXA8{bvj*|B5C^^P3I&dyG| ziEgShXU@DzP1PM0K6L0%j`Pxymw3}%=Ux_>{!VYQ~&DgxI2SxzT+H|ukhIGsHjydb9F8}3^eJc^?cst zz2@z)OJAc-Y#iyxRf}8rR99E|h*u7adjf`W9zd zw5MOK<64|*(L1kdzUcB&JL|g7qel$M+L;?}4b+x5Ca9^Xs+QUcMisNKQ#c5-)}~KsrdHxs=?K(Uhm$C78)_GIq~l$kQRz- zYd2L?RCqspc>n2Bmaag%zW#pqkPtTSazjlmEnW$Uka0zMDsncfbdU@3^YewA=5Gt? z=CdI>Oq&u}x-#rqa&KLgS5Wxy?b}v<1^==)CN<}^w5suJ$Kq!sdFA7l=LWdXga}dD zj(3*hWC}Si+7TA08rYrGS$ z^6c5O-Me=e`)w1Ce^qq<_3K@H{QTB4W0yl-yx>1{=oxuDl8>v(QZMIq4-R@4IxX-W zIl>+n7w4!DaLL15T*0{iLbH;ln-1E<%S6{gk{IU7vYP zL#N1@^Zq*eimE{V4D%)d_UV%n|Nf4ey1Kh{Uxh*W0~XD^JNq_qNqNVeees7gsotzP zd8z$$4Jj5b*#vF36J&m{K>eY-%G6a z{>$&(Ux-^pS#=fW+#1-_6-ZO}FJHE07**<gkr>kA#GV8q|gz z_6`W}iiqHD%eJbs73Gs7%iW+aZq?}?8@oeQRrSWy&;^0Po5tj05DiF{g8TMa|L#43 zr`sAulI|mN7CJk*5H^a6imv!;U4d1X-rEd&ZWFgGE-9h<;6-1F4Jl#klVlB~!eM=6 zzi02>4`GLI<@`{sdwKNXvwfHTa6W82e){yr!otGYz5to@n?IC%IVD*=JUkpL`BYDm z@}zt2rPRIq_o@B4Wj4vk$RuCN-lU{7IUv`GqiT2eZVw8cx4-{MEFOt*?9ZMk+a0|9 z;fc>q?-LZ1zkOS@!k@Foc6-Ay>b11TbaXgw+t?UbSkRFi#m~4W67Q0r)^szWxw#pi zIY{0fWZ1+l?OSqx9lxX`{#4$LL_p%HQ>TuvEH7El{V}oRw7Pe!*jAA5 zIJTB-)YsSN49v`Ys;U(6RO`u8^@K^)UCJI zu+S(l0Ac*-oQ{eLBi83(gv;_)m*u%#C}$KpIyx-lRz{lDn(uG#DJ?C%fqmE1*6v+i zTEMn&d!r0Rq?eV+lZ3K=|0a!8oqNH-TM+2y*3z-N=j2F`s*Wsm9|1-d6QHna*U(^n zbb3a{@%AhW#w}acUAuM-FZwd`TFa)Mo*sa+wK_UFi0CzeienW2T%K<5VWLIPrKGZQ z9pI{aVxj=HjpI3T67rqrr%z|dt5o=Hb5!7)`nLx12ns}ZU}&h%;GpT57l-^v#tZ$I zEm@D+PZk&7qt3Yd_evpu;LN{Q(MNJj&&)g#z;%>?nfX2{^&d{3-87dkUk;OT;Y!j- ztH|zh-Uy`m65$@>6nBPSd-m-sN3{M1=rF1d+CdUdO6uC;x;py1ckech?LBay z5-9JQ)SdpSKp|1lCl^F>)x}0qfERAq+Ipp=>{EUz<)a*a870uP1O~dc2GN>eZ`TcRkV5)zzIHPAf{B+7+Li?0$@=JFbl7 zVflF#mE+3Fn@9rJP|tmgmtW7kv%BYm*TnDNkrx*FYpmz1l)c@w5i}J6+VH}896gBxCZ+X5?FOP0h zQc}|Q?MW+IE=ADh{!lGi7?ZSfaOgu2eras%mrU*WGcjQ|a6=lw?#ppl)zfEl0CG1T z=+VoUo6r=Mm6hfH{T&+{dr)cm7ZRSg+qZNB>angOJ&U*l=gys*9BSIg$jF#;qi5B# z{g*!)$(DEj{CROWb|dBdg$w2G>zPeAE!q!$eNB&}`x`G~1jO1};9#2)aQf_7kIYOl z-6H3GeeMygNQ}E5J$bS@S+{`QFe9gJ$uNTr@jyP9gM-6P>9r=1p}wJ^>Tnj8Iwv=` zS|YF)eT&@r^9;zKo_GKLG#F}3VA$Z%F^z7>cdN)+A0Hob)&ov$lOdl@Q^5wjdcSRL zGB654vbd%u>14?+n-@3|>gwkHBNp{hYYJz3H*zuU*>7TELLd_r6_v!TpR^+*BV;`& zAx9k!p(;=)m$I%$e)|fyDAL8Gr3DIrVuM!jI zYfCrfhxQMke2s_q1T@xI3k=B*$;!!*#T}@hynns+yV)54RlP*}UQRP}5$DRjs%BRD~tNZ1pNC^vm_hS|X;`u0>x+y7=Ym z*ZZld`)O%u^{-#2MI#F~BBOSVjkF2QK}(vRS6bQu0?UFA=%K5OZF4_$0Usc2`h~~c z-CZ<(X=LQOl=C7RHlV7i>KGE*pTB8wqyPJ^*K%phYpH} z4e#2e<6CZqGkw_CD*$Lbp{Opo5MvH78U4Y3v##kZN2m2cNBuLYq;;o*L$ zQ)AoWsH^mlrSxcMU`uVA(uQGt;eS8;SJqmn|{geP`k-+e9Iy~&{Ukx66$-6K2vF|n?slc3 zUR&JU{Oo5>y z`fb~`O^$bQYinz(*8Jrnk)fgz%>6*KvD{D?wT^*}&D+(LVr*=D42bs5ojd8-*_C)m z{VP{?ZaMK-*b|+k{D~91=+Lly<$%YLsi~WQU>xUu1ha`;q1DbbFSk|iT<;-EV1&cW z*a=NdHhDhACr_X9vOY90HKoB5AUhSewXq*{n0}J?*73)WA2|*)`zw&x^?_QLJX0Sk-PWqy$|Glsj4!)d-qP?+#G;l zgP_)%)u3y7C41N11u;T;puYok{M)y$KA@ZLnvo%_bNR9`pq$p`**<-DMJ+`|8tP)C zn_2%q)h|$7Zr#4!FL^Q?o6|Zy+8!Djx|O;Z(N)o$dbtMMG3&pRvAn$eQ&(4nAE)HA zgoK0)Y_JIG@UtgR%4|gw_U_+*1!&J0Ep*AmKXn?yJBcqie$F!sQv9|sh_^Sv{}a0OmWfC9~zsQLi6$@C0&-KlH%jL zC7*vn)xGoMBg>(ywH$FNDW65f#J(-g{r$GIFtejz?|<*h-MvFN7@ykPU*M2tps>He z`F@N3_b=z^xH3cGjkL7n%Es6O5{-m3z*FbjPi0o*bt$2$v<1nwbT0xTnd9pvL)N;_@XwQCp9Hr)qL zIyoh!4HuI&O9q5^a>>y~3rX@A&Bl##2478r`--JUHM$ z7!QQZ9)7yu6!I`2pb|Yc>By0z^qUedpeqT?nls6vt&etYdE4sWfuR}3nAx7y84@Pg3U ze#lJKXeR>=LAMAx{U0BnwgMc^PmeyFot>pO4~AS8_zDybjeg3*mIu-hLTyFpk`ubt_LC3 z+SX=>rKGd;BHxe*&bSAUAhSG6jcid43T!aInVPWH!otF)ZSVG=No3r*wc2(&#Y#Fp zMKfcSQKf%*nGdt|(!7nPrsf3a=^mS34Qs#`l%ZpJc=*;&eNY4Amp2b|k%lrR4%8e@ zb!qJBU{J=MTScx+aGv7Lv}k7q^i6;J_JcPgKRTcf*SG*N>mmZWTvj;Alc3QWRL@%t zbk~QNJOQ4FbnjcX1l#LfeT~*O@WIEAN}oc6@4qm(23!m>$P{JB1En62SIDr8X8VpE zzk}`zd*W=O0H28&&W~NU#A5RLuVF8@|w*K%UaDuflI)YN|Xz?X|vz z#b?{FlgBSycshJt;?9VsrIw1;{AhMpcV8d1i;Igd*HP`C3oDJtqCBFaoADMCfBx8y z_mR)pzF%6JYwyM6;^$)5&{S06b56*~QG=SWTbdU`Kns3#(kpcQhc1_A`{=jwlakx>jl{8{GEG53#?0RRiwG8a3X0#%cQ7RW>QR^mlw8&m2Eab=|@u z^bWJ zy~5->UD&u+Nl7)|{c&ax_&M!zp0xMypR$7!ONGg4<3%f6=&z35ym?T1VdQNa*Ud1{ zi@9HuG!CG9HU#N1F)=|gA~H$8Wb-;~bkdzmAV}W7e{Txn?3_?9qG2!H#jK`ALIS_gn$f_w0CFxUx7P zbMvVl>#d?onwsh0`cK7tTR2g^e0+6x}?#)2M?jAk}doQ){ALy27x9bB&3f_Ehr;X)LgkZQ?z0Nc_QfJ zGK&w$3^f%M10=RY3z5%D%KgFb-ycEMkNsnDjuq_QeNM?c6P$8!$ZGDhvv!VCO&UyA z3jkoVKVMj-U-`_AywKc=Zrp$0r7Rv98NJ?xLYrY$RHCHOnu>~(cr=vqy`ZOiaQIcf z*Wp+JnrDD*Pk5IW8F`P0KHHf}y1MXWVbV_QIL8I*(`ky>jmYR|YCXL!?d+r~33^`c z9b#fE($dnic&iCyuG#tdDAdRhX{RGV<Kr4VKNT|B)F98UFBKlAX2ivm`ka(dGuI$Mk zwevbUzDQ;`=qzacbR5>MUCVal_U@M2g*W-*IX}vOeSM85=YQ%pFgi*{VLN=&I6NQ& zTGoPwcw~Un<+pF&lHw+R@?;bah(Z9@8VcgAc`i>7S{VACz{iiNnVFesxun?I+S{dj$d;1C3rSEW-H|wZURV|019~Gw)vH%L z??8^B8ly*CP4TeT6F{>J#Q-F^X4ZAB&+DpYKT*xCb{hO;d+E|8r0UY|)4zWkU?pfN zl1}rYsG%IUZr(Jcw0?ZLcdL;vuWn-%Xl+^@jsHLyZ0}zz_*A^dLbHrw-4B58s zV@=JspdF{#C|8V)%kZ1qm){Fb|LkI?O!rqaC%mg;Gr}WX0kiqKu`TT&G6K$Lp<`~_ zlAgU*zvcioj{s2oM7V-&+d*TR zzmt>wS;*thK!5)nAIA$Og8?GAvE9<#{39xUZaXsE6^bU&$Gc=!7S;L!6rA2P4&)5W zwVn80QgUyfX8N(-(ThvU_~BoB8HJ#<;h0m8HPIF%e%C+^0athhQJ)OaMYuxq;zd^K zV&}!*mG%Rc@qqtpUCYbL6l0eFtK(u~)JLh)-rm6XC)YTvB$$=p{qY@;d-b%}5AfD1#(|k=*nnk=2W$6SKqUBz|2hJ zp|ttG!aEq=*j>IhyeB*;NOe>+4@41>g0HotitTohQ%{kJc>I|E=`_@P2*G>md5vv^ z#l^jF1e7DB{jfHa?zmGdG^?v>YfqweBkbk}dYNu7Zs%fTTxdT&Am}LF`vY>{zh6Z` zd$SgFl&P5++z5<7gcMh@N(&d>&6qTO^n^l^l;QW_h|2Cvo9AB}8{HMRC*J7-e{2nD z#xj2aXh+DhLxOTlU41jkiuvXTXu2pAsLa+Y%T5%ru8|8a$EeUCvjTaeT9aDt>+73% zxi010^1QKGmG$4VsIS9cc}`tOQ&3X+=pZ9O_2%y7YYwU5;hegKj^NUDFJANn9i?Ow z`8aWh7I^plPp6aGo;Ee4fl~&xR0a}(iUNr9;ZvvxRsP%(bukX(M7vdybxSw6oZnCh zDaXQ>He^|Te*iMa2+TIo0DytA&(?PaU+9~^HrxjiUQ`LO;(@+_{31z`Xxz~0Q&Z48 zQLH+1{;M7I$pT230)N_XEASNC>_J#WwxOvCM5VE zpZ5WB1S&09-MVwvOicFy7PF% zni}-f(ha8s30Pi0qR5n#%SPK+Z1}Rls6x_l$L8Ofx!`wvd1j9$ z{x1+5``c_JA_}-$@P($33v6AIE~XF%K+NmceSdz`bS!~{zF}=m#J{$8X7>^Bz2FA$ zCne}d(k9;%z3GsGwTNM)%!*ld#cwUy{-=Am6rXeMF zWNhBDrA6|zT)>NSWY3h9pM6ZWM1}Lp&K3tO5k`YPCYBroDFmHi$%hYey=m7FtA1bW ztXB3@>7!4jp{G|sLIlO2 zcs+3E=bAtP%7rub`kY(1{5p$KX^( zMn=aSNLa}6WuPuw@sS}|CN))6!!D;8P9xt8&{ggkt>@${K9#=gQA)z!;HMMX7{ zYrg>yl!G=Z2isKwRPm$N=^Mj^qg`EG(=#sq_5W$<9&Se^F8T5$y|z{akqZxitB=pA z%&&th)9^On^pz54k2@2x6QM$FU6AR>0$HTND1`)m$l&bdp~pXrstu2Cv9hv4RpnMZ zcP?#OTTvUQia>qB$hRE1#{aM-^CQp%FO!cD8oB--KOzBCK}skpDY->N=w(_t1rh0t zq!YWzK}OqYMQu=Q*2B&F&^wFSiI#d*RaYN>d*$v%=RvIV_C3tn977GaU5%`Nzh_M&m3Q)yG9eE`C0l zow9wfX#~Zlr1(MgB~3dhkZwu;PeeA@wQC)kIKqkGc)fY^=0;EH8hB=d+e;OJ3Kylg3i!E2WnUTuJt%)9J!xa5}T zlYVYqUh7(nax%7wnlb?7y@H&A;~0sme&gm%Sul}zM%ykJUA`BPVLxSFR#DN5H9dHA zM?T_@wBG11GL;==WBPzl9b;T&K)be#<@Gr`PimHujw z0l90Avl*#s>6;*8C#I(>u&ecNub%VCUfB;oF9Cl5A!s3D=%86N@9V4HhOK22HeCG% zjmgwd)66vYD=XrhfjlAyC~@=V&F=pG?QgeM_j`zLm`8u+cj6(d0V+|5$W{Nc_B499 zN_28>u0h%dvh0n!wIe7P+&wxeKI}YlbVAEFE{K5)3GkN@Z~_Ip6wNE z7gKe7;CM2E6DIRO?jAY7UZOBWCM2u}FJ&oh!*>|O6cIEb3ovkT>5glS{Nso|!k`Aa z>AZ%97kKjhy7_P^c>IA?hG<#a4qYz>2FnSZnnkW~g}cF*U2FsPr1YH3lKQ|ImzZT_ zJ|eEuvI$W@M4Y^S{rdfRBjQg|yiBYDjEwi7eG$u$X1<+pb|Py+JF@&25D_|gcj!P; zca-k@gKm);sG$;YB*VH7ucz44-_@lc>+n0yq5%wF!u-SiA6PVJ%~6C8D|b~ zl*AB8ypO|fhx>w;6e&F_Qs6GA|jUXplf~Th^D1EQjuU~&+bv2`f(3F7_ zfiHXY<_!}>NzL4wCMik91@t8N&L9Q!VA}^f3nV@1x&0aWPZBK=eB(x>5JDHezx~VY z?bW(oc#^^xwL^c^Ro~x?Dw~039|9R8TJTm7 zAwZZ|i8Ol4$Al03*_LIo5hRxmNUFn!4-bCN6BnoZ^?DGRDfwmceF)(XH8mTntE)q# z?jG!_XGaC0!~X;L=Y;gN8kkKGMHzVw#C1p2r%%M2MszW7=0FChaIu8zHk&yW`rnhakq2!8>vuxm}DMcZZC?fB5iL zJhLYde%EhlP0))(ot3hmq=7{RhAt1FAhKYnX}Q{3=P!ct!deijl^lDJ!n5x__jbH@TA-3Y4E|5ckkYf3jQ1=sH5q3Mzuqp zKR28ltS4L@*udC>84pE&3=H(a4SPF`bjc{Nx_x zFo}E2yOj02i|;|cgn8AG3i+Ez|2VckCC3zi4vwQQ0GUgyN#wi94#2x73}wzfKz!x= zyGen-BhtaTJDREue_c4m^sqeV=8p}lR;{`*KWS!s{nXAAaK^cB+_Iks9~~GNsCecKdM#OG?s{YyY!Wq*4Q9r^6S*Wb%>u2D ze`BLM$}BIE0RHef#L3A;AJlp$rZUl|nVXa^(t)+}O+er4dR$zao(^ z;yGY;BBC%{X++l$zxJIhD6+r}8AOpMI`rGOZ@&Wl2MU_V<(DpPqr8fXtFV#YyuoAl zzI{KY)wH!Agv4C?_P{|B1|w*`(ZZ- z3}i$joI}4U{D_Skvds+D`Au;3HOwr|-E#Nxa`|%0LzZ`X?E6C?1atw^h)1T+7gbd5 z5#NfQ^W2V;e(Y2Aqj)s~AU5L7ijMA6$PRw=$d&XB@I>N}-FyDLO(FZ+dz)*hY~9iA zD)P{3|6_|d4~05~Lj!hXy0_;4Wr}z%YUYMsEed{5JYrwrZ^pTun>XZIeaGLj1C{J5 zKK?l|!KbOIDR;*i;MquQgj1eIf22FgS-%!eVn;{EdGsVjE6bPoPRv-KLV!1vx3_;^ zcBKDP*kNW=Z(zqD#RH=->cU183HAvC1`3dJcRUJWXrSSIZ9ODErk?`VG^juJx`0BZ`;OMA8jh3DrDQhPw8fJ&%XV21MQh|n+_K9Mi`=bg7Q-OQ6w6xfO zwcv|-0M4B#HfTiyKNb=d9yVT7m}60+&qG5?kPh$}Pc>g4<`v}SSMNL%;tIOP5Oy&? z=Y~zPiXX5qH$T83hX}5Mw-03~9j#Ho%XM>?K^1HuX{EJQF@F@8fbYMw#%G!@VDf0q zvpsNUr1c(7jRAzFjwxAL8YhALEd?acpP7 zIopv|s(t(RC8W5;CMG^0m>xLi zPMC@5pU*jT>Uw1G@ z#Df4S?39fj>}PB;&Qee(t^f3&g6wnJ#>Jf>SFW$o=eoKLu?KSyJqJEf-3|6w5VRpX ze;Zu4$;iy~K$|Y_mE9DJw5l_ZdyjgZ;oqO1Rl9ca$`=9?s^W}MHg4R=A??Vvyu5sH zm{#Kja#blHD13t+pQaDHEIUGz=*6n5b_Ih^T3lKpsNQj5G#eaPcU;AGcTs0jR)i|j zO=?()CWW1bfw>rk_HK>Z`SY&e#EHPg6k3=`)+Z8{$EQ6Ong6lF5~dFju>?~9zkdBX z_)VPGT@Rn9PMj1ZHxw>SUlP2OMZ0Cc4)K|#g%X=8Dgy|rn%hMfbqNu>#)`<~{o45p ziN)K;=bqwrH%t-$!8 z&ESM4kh9$g1cYn>%Q;6XO0#EKH8{#(Ev5r?> z0C75J_gXpJEjRZtpfn>V=RVstM|bR4gOw`=lXLeo9M9fF$VZ0CHv#fi_}}JjKY*W! zXC4fEchwuK*X>0us(7ifudD&}8d^Hp*xQ3&s4y1NExCc{GH`V99ykz?O$)?!0(pj2 zP}3FCUI{Q9U!ZrklsiK;9M{rSNN!)SKqFm6-1yQD2){4y%+&Ca6L7KNcvFB9`U~E2 zQHX5;mMOwGryc%}v7Y=K-iUjUoiOH8du^xezwspY5!i=G^#$OCWx@mB`P6>WKVBDKZ2~GuNxmawTV~$ z4<=0#f^io0KRzKoC(@Gw=oQ<>%xu`W@dGv!;AyvQ%e)uewzaUTU$a`wMkfwsYwfk> zQpX^J(&YGkUS0^JUHurg2s#@FchQw?b|n^tHG&HWaXPvbyTC?%r>;o?-U2)5fV|aGL`NIM&I|RTw9G;6`5s zUG9saOx|Va%?9B4@Z#N)A02?zdhixGcE9<$R646Mt%0*l7%L2HtV0V)qRssHi6v%@ z%r|W?)DwP_46Pt7J66u?17_vi{TtVD=aIuDsMh*cR&_N)7IY@C6;4dtAZ}OYoo9;C zI`$i`;*_mgQRr)wF+e=R?RWyS3)tCgdnd^Q0s^JznaHCPL8LC?qUgg5DCmHSs6Xtah_E zvFFB!2r0|bAsBgi|5Z5Bi(&Nv&KgGIW|_yW+b?G??W?F z({v=`H_*mrvP#&DV4AuzJw1IL)t~&Bj!P=U24lMGJxXVOk`55Gy%ADK+MSjEQG~_* zY7IB1Y^D6{>-&J_Xe)&kUZ9(|ZfPOwwvwMB{}3&cx)^XcBs~0bf!^>;LVUbFhz`OH zXIcigEMiTKPo6wU%$kBbcg7vBd)i{eCtt!D9kuL3ei!A52)}pj?9$O3wk+f#?Hk|= zdM($jStCrj4sVpNa0Lj}P#p8G4Go6y_I;|Uc{sPQARh0>E9WT$6QAV*jtqp;_LJ`}9yYydNaF-(JES=G0eJ34fJ!qWpw#5e5!@RHPB>7BBhTZLJ)BDV1? zR##OO_g6nA14JYqFp@LnYzYrGW?$gmtk^gI8}mLezC^{utfr8_f$xR1ooq+#MF|iF zW^K;%LsRJP;c?*w^LL~tP|9+s!JVOL@KBMm0AxiB85SUwIN<1~P(YgL`ySuD`DS~P z@+czszZAjl&%b(HA{xX*Mdi^nS%cxTFS48xgU@^syay~zY)JeGLUU7-lMI+#21REC zgn`MW3U~(5o=w*dRR0xbjH-%{ci|(*4c4_tpkOjm3M+BvtAW=sF%-%(L9JCsj}{!^ zzIy$-FQH>$FPoiix7yZN0dPTi)iz~A8Q(WZxd8+xl-S#%dQ0ANb&(5(0sGa-MTcSe}FFH-VZrs z!9Lbmun{Ni9mKKPfvN>Od%B(Ug<~m5AjEioipf9~RdKb|1I;6o*wYB~zvBW-lP(h*6ymBw}(K;;A=7TauR?3fG zJb!)=@cYj1-tr^duN5ohy;ouK+wU8MC}LYB;RsW}QhXLpc_^&?Z?m#er@aor*u4a5 zyw5U?Tb9H7zr6su1<%7}T)>b{`vOg7fbg*n@9%7>fHMC9jFx7aZb69R_TH@5adB?| z(pp0INlU+^(#JVS&Dghpzdw~e^r;S~+S=M=K>m)=_Zt*pvAG1M^r4@h37qI7IOkJ# zvC{rPn73?fF0?NC7RPvN43#Tv~)xcSMb7?V85@+1YHm@2-*s0*Z-k*efEqM z1%e9dilAO02aKJ^p{!(IW(@FQ@Fw}#PfCZq{%Tc{oxiu6`F9@Zj-ztnEkz~%DcRxqB(SrxOgy0 z;qb;Z&awT^vY8}4961@z7o(fiEg|3}~2fBL`Wt-;E|uuOq{`%G~4fFT0mt?W;i zc66YcNGBv9Gyy!LC3ed8w+cUZjAOiuTnN3)9X#epXTdh86{_Fr(5XKP4PDR7{Mi27 zhOb`^!EO5hDG>Bj;VqSCID7^e&$9#efEjt%Q3vz&1a)LyK>LGJS|~#94)`CWCvNB* zI(5+&A;j>DP_TpW)ACR!@K4b`(2M{&l{g7xFv0SN)CnZn{>^8lziI+fN0^zLA4Cg1 zO&qU%Rp8hRf#F7Ac?z^^M~x+B>khj?7q~eN|DBk4G%KBiKI0&AI~j*?$XDdIQLmNqM^UU$ zk}%UMC?yqUa^=b!%r=0ruE6@KkE*{B_1z}!|E;yvapLcap2G|$OVhUkx>;$FCz0#< zPEbW}-gsPnbE7NIv^Tr(&IfJ<8ccl)hK3gnPe)aRu$@j8W@m8NGI>J%ywMt!<8m1f zl^!2a3=Lgc>Is;WEvpT?>N=qPFteZbQ{mN(BZtFnmbYkO01&4>5SZ^M&YEAL#w9)G zN;5|3#%pho>eR5`Ri&kFVC2e<_zxm^E5rK5?y!sowEA{@mCIkfk7JVvzxaC*DOeg=YU9VdT<*nCPIGyEqy<=8->bW`2C>gnwl9Ecd z8NLW+_KpAcL^K1%<>hOzRH?W58P4+Z)myPPzz>1em4MgrMIFIEMQ5PTfh8vX%N}@! zKcHdkoIU(*RLnYzwYBz~S&~8j&BJi&-6PY4P_z|5B8G1pq>#~G2?+^OHKQ7hgr$ft z8Z57pZIQ#JwnK|#x)}2a;O>pUxQ0krN6m|7wZvMa`SiV`;vN!HBu>=XZfbdX`EK+k zvtxPVucFmvQYYck^v1Omq#UYb@$^Gl6Az7#< z7;`EZfg*i?K;i|vIW{&X;V>;YtjM8Z^gA;32Utu}l%e(LgLH!4BswxWs#R&|>75P| z0>c=+PX|O>)?y&*0|bLc@w|i-Vh%uNC$2!^1%v>grO=zh?!&$NbvX!4VY>-^LKZ_v z`_fu&Y#xNo?}e$T?CD;OFCZ72t>y95UZ^u1l6QPjXmrR5Ql&!=Aduyya z_F4?cT|SKtpB^9J2~r<40?`bb1K-KQh0YsXV-1sivx+=31UC$a^n#@#mlX_4L=jOI zvxbDBM4f8Nmvv#(AyzOMjy^1u;$r5D<_Mo zHdE+!a150wa66S9c@V$)crmPMWL6ZU^ILy@c8m~^VHskN#srIy!}Rrdb%Ev#3FRd0 z#|{jR(ZFYc-|d4?JJcR}vyR*mf(Fk%bw`4lVOm=|F%p!6 ztRssfa|6Dacc4p@$JJ6a(zu8(AHo^@^Xyf-E9w3AgUeN<-HS;`=gSR1u>Fm z00awP>{jq-kbyoOdDfgTL}TS4OGU|XS&=3qk>sicQBm~~(bbkB#|*HjzzG zznFVbfJQc)eTW6pn>7qrM9UD2J-=cDD$B}V;fY}$f>bRCN#%sn z#lN_j(wQ$qFlmJM1TpPEHxP zi=jDC!her+V-gWfNQVlYaDWRZ7{G;SPVk%&1`q{6NKs2<_vQ@<-bs&uVQ3)`89>)2 zFbC7qL1cTtvR8LrjL9dac0?`h0SGk=?~u_{(uo6|+5M{ffdUX$AiGa^XF(@&R3CF0VbT^S#__Ub2l$ zNq`aMb5n{f1v&I6A|e1_1@0?q(8eICPQZ_``nW!)#&ChDpPIna(yA&NICtbBeaS1A zcOp=PF(0BB+c3bTmY{Cq9IruW7~BcTH@{ZDztb1Qo;3+ZANH0#v+1cBZkskzJb}}Xlw!$< zJS_No>F)5UMzq%*HA1h3BS|8ih~Pb=qkfozim!{poKq!SRKz(*zRe!r_6$+uj$giT zSN)ArESb&*bN!h|>+`eVU=1^I4;~uLBIl!vi;HY=4P5YyOMq^|+pLAyH}&WJ?at+4 zrqxu$QA0i#*4Y8BV5L|#fL>r>wwOMN&)srM`;b_TU|2qI^u{TM)x;|U+JaSB>oeZ( zb*Gge%__8+ZYa1S%CVO*x5CK6vIb_7DRxQjp$j#T_lT*{@l51-9!QoLLSsD4Qfan{ zd&mm6=&)e`^zN@6r=KeA0A+#68ucO$6-&u7SaYA^ayK8!ws zreIjzlUX~OcI}DMj-O>``3#Cw-VHBs`4C2M2^bY_uD~P(k@@`n{cqg9 z?e6FIdD~S<`^nu|*T4NK{hCUVvUR_S!7)JU-xZnWgk{1aQA1}XvvR+?OQ?z40mzSt zlREne4GcddQCdMBDaNYehL#eHc%oHx^Yfzvf?$O9#8(Q~L#BqB51dUZSXo{m(m7JG z3LyKJFJElOhOeoJXS9JKTu^py_&*T1Vh1vMue+ zTn-A#oqK7iEVM#?zU)3VM+o0j-Fl77)+V(u76`fkNh+> zk4|b^KTFC8Y9Fv90|NsEi|RM&7mHMmix+qf9b(0qF9RtWsV+e779wU`Kv_B9R&pm3 zSjrCoeM^9ewUOZ$4>pj-iLndq;{ zT})&aaq4g^x`wA~lR)*OFd`^aQl@;!j^<$O2B?k9mXKA&BskUzzB_opPQqXR1y>QF z3S3;O5yhn8;_|}ykjy5Q`(#;RM+5F`kblN;fUFPA&C|J+W%F}s z7E1&2*_WRzB%oM-#joxL@j29#q(G^{XaUIW(;#4OOH0cx5Y8Lu>0cv4ntzK$<>!aNkaq$Xukajaz()Ea zXPN>IJc)=nptSa>(h1~gQRsX~3;YygBO|#k$G>!tmCik(O`cfRNW)GZfq@E{(HBvB zDpnO2rm1v2*45Am#_Gl|+jqL8u5F8}k95Yk5<4bcw{F{p)XU4w%}p*l`CjOB>9I+Y zhf-`5*o?2R2+4t7S?#*|I1fZHZ7}%NKW;WNip9ZRN-n&Bs=LESUKTe5#DW!sjb-bw z>62&AP7*N?q~&9EfmpDd=h5pAYc@TY(fbRhhA99iAi{yE8(UW2qkMt!!)_(_dE&g6 zJ?6wxF6TYNMNelb@xU4H49hnF@2J=WGo6LVp8~?D5H)4$fISGtUqF2^stKBvNrzw) zn8e(MDh^2XmoJY9N62zXB~evdTM4{L|Im;UlaD)6;u$zYl8p+OJ6#UJYG?+0**`jZ z){WMa%Phkr4yo-1XZk;tBtZ#^rNqVw*Rt8AUG-aaj(3 z3lTZmS?~hscGz^hAC{j+^dt=}Ew4c$HUMU7DcJ8*iVY(B31WgET153Ef~z^BlWkn) z63bG{*RRnKfE&OJkzdY?UD3(BI}@3znlOkAza3$GP)O(%L`8cz%xv7h+GF=8t9L~_ zlIM$rzh4y~Y*^6>%#1s*7^=|Fx3{~X>7@HlUPPrb1&wWd{rVZ)jQ9%Hu;xJBjri95 z6`g&5E#;1aOM`Z)DDboa+T^uQZ6|DC3ywhve#}F&nT<^m2RfO*;8}maF=8zmgke4U z0?o<~T2{EwfKf*l}a5(sbEyr4H`p-vB{gi_VC>)USl%8G$ z?lpO>ZSzXg25MCl)DvdPb0Gt_i(_4FZ?Do<8y{pBHGOpPXVQL%PutPD;ybumwrqLp zA6>Cf$2u;f@vz|A`Ez*7nNEiQa`#V5tsc3S4@}F+&d$y!m~KDfuMOq#*U(S{=IqQD zOPtdB0OgG>EtR>QCIc@fXG|==Enlc>x|t@^Xk=*k?xv#I+Xv#uaY$~1KX0}kB)@HJ zEDx7wqS_@*jDVd+WfsN7ZE=U~R_?B7eR;NgE6T1Ql55Kr1?0v|=UY#A!ngDo9YUsQ zy&K%WAwLK=C1%+X0)R5qFF6fL2}d*%?d-vXx&{1~X&9ezcj|o6-M>4TN@WdLb!X#@#ZLZTTztE6EunGP|Bz z94Lr!Ytjvi|dPw_@nVn025if8j2)r?no8(kf0_}4_!a_@2O(8ZcW8;{43@3@_A zaWgk;r*fDgrK+-0p};CPKHeKZdp*TwtYa?MsTK!g7o;h4LM(vD=GCPQs9M^ON}84NwHOfSIDAT+_eg!iCi`oQ`0=+_dhaiLvn`oT?P9w{hA@N1Yc#VpR9! z=H+QPBE`^Uo!1n1M|ILCv0a3#&vHkn>2)Z*8%&pbyNDqS#YP z7FG<&i`2XL`&a}eHh1y!@u69PXrr6bz6R5B#N!8Lvj^VXp&tkvM6U=KPT#PwFr_YL zPEJ2q1vRd1YDSkFP_(iPj;!O0X*m1`$96uB1SVilQDcUd)d!#G|Kr=}!V_5$_NA4T z>ma8!k?>%EYzo_CC8&s|n((l&8$*qIaK$Avu$JlpXt0P-JH-8Hk?U4x@z;X()T8o-~ zaXpnqFFi{k7BEb3pbbn7WKUiwLdORmA~j|8@ne|sr;r;N;PqHjJht*`|73(Wyd+ry z+>aHvqt)@Q@>!M`<=1Q%io(T< z&H`0c4z>}rF!cb%zPO z8J1l+t)wIb$O3MPI7IQNsAA+dp;VLz{;#IaJ+9{akK^CDCYP}JA#71@5!OoN(nStJ z&W=)|GL}_rDa<5F(|%-`%VLd5>8et#QzX)2*sousIx#)`!kWp+Ww!dUqnzLK9e@1( z@_3}q`QAS7&-?wleFs{27^kXE)Q<^e(N=dZq2DoyHh7kBU#juPTp{$Ni;Fry1L%|6 z0;HOK{wjnh5geH>+mN`FPw%v9uPYD0o54*d;OUgyeiNxg`pl0iKWwj3srI zC_qI`&p@`iMDJ>BuFKA7XwA@MN%#ooBP-W7@yn$XoR7R*zjnHVu6M6qL@Mm2i;W-f zh%Q9JH%NizS8tVWGGlubpn^{MpG0TzgRwL+DCtCp<7Od>&Jt>4U*1&(s)AkvK_c z=HyRqnb7xxFF$a)d};kGzrI~0KY)(Ie4I6ZPl7m^W2;b@KW{qzm7>@t_ChW$a&LLi zBXKq*ltH+*Puyl~-$Z7@+YpsreSVXyS*l9X*8dJ>qXB?BneYOYM8pm>wZU2yA8~Zr z;pnaj?y+nT#~F%*rXZ~Cs$+lTEXOoi3|q+s=Jhp3Kz1>=iWyCkmshX;0>In0%JYyR z3SaQQ4?CFg371DFMOHZWQNlXP&h~Ji=sB*gF{+;4YhwZmR>m4Kmn-_tI#6f72Lmk_ zg2_^`1l;lsuB~&rp}QA86~nq2D$_QE<3chrs9O*FI>QFs?AqDQ>*KEn)ui?Sk+t(} z+&F&u>AuAIy5X)ty!~$YZ&&UY=kK&3F`>Nh?cliLmViZz%vfj@Y$@U?@Nz8Ok-#Mf zrI5@P!1+9FPb-?z53FAuulh+{_CTP#WKEXcl3#$xIDGLc;%U)U!a<9Hhw|s`SeO25 z%NeSoOS1Y7I!00Q)3KtQiQ^v4O4HoM@i&{%BKo6@kL}i6U$8aO`Nw-f_rgmIo5xKq zFs{FS);0OF$IT~dIR?>#nmM(pEw2XL{oHJ3Ih{Nw=Or3f4EdB%@jHosR%AFM=O3I@ zwGr3}A?1aJ2LDy|+GOJZ*(<3ZXIz(=;q}7toT{UL z+5Kwp57K`2;}2m8o#t7I01TqVH@F0umMjghfD33YWn<$EpEBx`T;$PyVQJV)1Muf0 z&`#1|Kd4Eaw=*uTf^U990n2no48#Q#tW_!JtfG;0O9qAyBdM|iO4t16hV!c~_Yug2 zWmcc`B8e0vNdn~;gS9{J7D^R7UPXQbOGwGQy{;^`|Qh(&b~07@dcB(qjXNn5YJ$Bl7o_nuW06Ro@> zV5sFp#|dS$dAia~q495T!D~JXA;MLgsJ7=%dp?GK8&R^{ zgUmJdU+;VC3&nsKqRZ@8b#%tE#v)Ja0aG0vYv_0F{j*1vfPgay*CS|FH3uy-wzsue zx0mH>ykF$mT+};VcB?zItLxF+dBbKzcpg8Thgc|kx}m1N-Vp0uSg6(JY}iy7<=?un zZhzZ) zwFtU(y5Q2u{m)0~R~n0U`Ly33wE5aVbCd*~v@8=`1s8KYAGchwaCu@fgQYUyQMa>$SufAO)?=rbpt)GQg! z%*2TVBYIoe^+XZXFO!E)M*#{c{q9k-@9A~JWt(HNn4Oy)FFh^l>y)zh1M7_NaR<;# zv^G_Lj_<`PNp>&d<+k-`plVh;Lb{b5lL7gJC~D3<+^Z0yXel z+42cg&e4?mfmxo`)y-=pAe!bBHIZBH|4#>Xd{z@mrQr}b5C_*b9P^!%rm1G^nmg)1 zHN!;WJt1gh6a)883|ujmbIQrd!P>EN-qwbwhNy!+&ywD(n*4x~>wa0t`7QJU$JAr~ zz>!m4Ual~ejClAc?{AdY0-7UE8a{pc^2YmV=fBL&wZS0FE+CB{Gs2|G5yeJ1&uUmb zY|bOX^eh=zk%fOaZ1!6`!GbUlho>(!7F>KagjSww`2AUwOsw1jH4R(h!*@>hU@p2+ zU;izD=L4=?PNQdvCM$>|Vm6$E&}ek?6!X^SPgjMYkOx@F4*jdr{Pl?Z?02ptmXvtM zPan>u-A3;2C;C?DCHT($>u2uuP5AAfG3K8Cj|6heY0x( z<9#|ewtuwySyFaj!`4i15sYo#vgJ)hzMg0OMpMAvL%^O|xD2tL@K>fLCdX1!tw3aa z(X~_^nR)=qUe-zr9Dl){78P5`1N>c9>^Sg?AIDcRe|JC$p10L`(0{WybU4UNl+9Ya zfxd;o&7e7U$eC^J?ML!g=D6HOjvigO$LnLF*W3YQyF^Dkdz{-EHB>&E&1obFm$nWL z8OYggG!=nP6$6O*9s>}Ir6)4&(E|(Hxczgj@Ij+_wn@RZx-dbv6L8VMRG!7w*Cm>X z4F&7$ulMcy!SUNmh~nc&*ib2%{HGSjUGAC2|DYKOLgFQe5WoYEfu`}(EoWs~2NLR? zUXtuD3U(U5`8+MMo?|T3@Ptj`e4MRPX_&IpEzj=bk;iH|WZ^fHwu9=JD8OXZY>Z>k zM|$|JwguXu6_E!)K7xy$!6CdK7FN)-lwi#uSP#b6A)TB-#00Yoc{I3N-S$_{M7R1; zef#a&n02_OMnfz^BN_ye_6ZQ4o!wK$`B%mrafv`bTO9bw?&xTmTq?0kWaL=`1L6>f zBZ>PExH&JtNtCrQ7D3a$Dh3X7g*_Fp8_KGJ`he1TJM9F67>!sT6_mWc->nMlZd55Ky-uaEQQ1ijjL3X@kOyQ#>`d7HvMxJoDilNbdx;1oy43Sv!w zFeU7mq9xP`){Q}f2mb)2r;rYTS5SxxPTRJa zm(c8@67w#M9bU_{^^o@K?v%eFvE~R4g~g-al1y2F#c8yqCu0aP;V@)l$5Ti5kKsl2 zifK_};A7holNjCy^vkYv3EUZpj(~P>rb`2slNWygA#_HbI@hZIb(h%*UX*EFD@2!2KN>>t0_~9 zGbJUKxK6PFW@eg{7Z!ylxTkG%=p|}@&<1th&G~7XgVoJPxFZ{s5SS9N1m>gwJb_cTt~)94>G?Y7H>u%Va3in_d`=bxe8^Z!Wx fx#I^Hjh%hZt<(l~-}scb9x3x?d$^r+T@mv?UX-Jc From 238711a117b985b65435d271b518cd1ed3316eaa Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 8 Nov 2022 00:32:24 -0800 Subject: [PATCH 05/42] Trying another approach for pipelining --- job/job.dot | 10 ++-- job/{example.job.json => job.json} | 32 ++++++++++- job/job.png | Bin 111032 -> 116818 bytes job/job.var1.json | 85 +++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 7 deletions(-) rename job/{example.job.json => job.json} (59%) create mode 100644 job/job.var1.json diff --git a/job/job.dot b/job/job.dot index 3d899cf..aadc04a 100644 --- a/job/job.dot +++ b/job/job.dot @@ -30,10 +30,10 @@ digraph G { // - JobR [shape = box] - JobR -> {InvocationR, ConfigR, VersionR, requestorDidR} + JobE [shape = box] + JobE -> {InvocationE, ConfigE, VersionE, requestorDidE} - InvocationR [shape = box] - InvocationR -> WasmR [label = "exec"] - InvocationR -> {g, h, i} [label = "arg"] + InvocationE [shape = box] + InvocationE -> WasmR [label = "exec"] + InvocationE -> {j, k, l} [label = "arg"] } diff --git a/job/example.job.json b/job/job.json similarity index 59% rename from job/example.job.json rename to job/job.json index f4aa8dd..6aa8ac2 100644 --- a/job/example.job.json +++ b/job/job.json @@ -19,7 +19,7 @@ }, "run": { "wasm": "bafyWasm", - "input": [ + "args": [ { "w": "Qm123456" }, { "x": "Qmabcdef" }, { "y": { "with": "dnslink://example.com", "run": "dnslink/resolve" } } @@ -33,12 +33,40 @@ "run": "dnslink/resolve" } }, - "after": { + "then": { "effects": { "bell": { "run": "system/bell", "on": "always" } + }, + "jobs": { + "left": { + "run": { + "wasm": "bafyWasmLeft", + "args": ["bafySomeArg"] + }, + "then": { + "jobs": { + "subleft": { + "run": { + "wasm": "bafyWasmSubLeft", + "args": + } + } + } + } + }, + "right": { + "run": "b" + }, + "end": { + "run": { + "wasm": "bafyWasmEnd", + "input": { + "foo": "jobs/left/subleft/" + } + } } }, "signature": "abcdef" diff --git a/job/job.png b/job/job.png index 7ded09f7011639cbf8180246231ce9765e7397e2..80ecc60bc6cb67067a6d539b7b37f884b2a2b03c 100644 GIT binary patch literal 116818 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

2UxF5BVQVNcOWbL%QQ-QBG715ADh2#S;lxKmW8| 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 Date: Mon, 14 Nov 2022 18:40:17 -0800 Subject: [PATCH 06/42] Flattening the structure, did some more prototyping, started on text --- job/README.md | 45 ++++++++++++++++++++++++++++++++++++++++- job/foo.hs | 40 +++++++++++++++++++++++++++++++++++++ job/job.var1.json | 51 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 job/foo.hs diff --git a/job/README.md b/job/README.md index cfbf8f3..3aa1e43 100644 --- a/job/README.md +++ b/job/README.md @@ -5,6 +5,10 @@ * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) ## Authors + +* [Blaine Cook](https://github.com/blaine), [Fission](https://fission.codes) +* [Zeeshan Lakhani](https://github.com/zeeshanlakhani), [Fission](https://fission.codes) +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) ## Language @@ -12,16 +16,48 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract +An IPVM "job" is a declarative description of WebAssembly and managed effects to be run by the IPVM runtime. # 1 Motivation +IPVM provides a deterministic-by-default, content addressed execution environment. Execution may always be run locally, but there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. + +## 1.1 Minimizing Complexity + +> Every application has an inherent amount of irreducible complexity. The only question is: Who will have to deal with it — the user, the application developer, or the platform developer? +> -- [Tesler's Law](https://en.wikipedia.org/wiki/Law_of_conservation_of_complexity) + +With "jobs" as the unit of execution, programmers gain flexible cache granularity, parallelism, and ___. Partial failure is Transactionality + +* [Convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration). + +# 2 Dataflow Graph -# 2 IPLD +* Automatic (and deterministic) parallelism +* Dataflow / job graph +* Effects System +* Partial Failure & Transactionality +* Auth: SPKI & object capabilities +* Wasm execution in depth +* Spec format IPLD + * Input addressing # 3 Acknowledgments +* [Quinn Wilton](https://github.com/QuinnWilton), Fission +* [Eric Myhre](https://github.com/warpfork), Protocol Labs +* [Luke Marsden](https://github.com/lukemarsden), Protocol Labs +* [David Aronchick](https://www.davidaronchick.com/), Protocol Labs +* [Irakli Gozalishvili](https://github.com/Gozala), DAG House +* [Hugo Dias](https://github.com/hugomrdias), DAG House +* [Mikeal Rogers](https://github.com/mikeal/), DAG House +* [Juan Benet](https://github.com/jbenet/), Protocol Labs +* [Christine Lemmer-Webber](https://github.com/cwebber), Spiritely Institute +* [Mark Miller](https://github.com/erights), Agoric +* [Peter Alvaro](https://github.com/palvaro), UC Santa Cruz +* [Joe Hellerstein](https://github.com/jhellerstein), UC Berkley # 4 Prior Art @@ -29,3 +65,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * BucketVM (UCAN Invocation) * [WarpForge "Formula" v1](https://github.com/warpfork/warpforge/blob/master/examples/110-formula-usage/example-formula-exec.md) * [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) + + + + + + +https://www.tweag.io/blog/2020-09-10-nix-cas/ diff --git a/job/foo.hs b/job/foo.hs new file mode 100644 index 0000000..0597b46 --- /dev/null +++ b/job/foo.hs @@ -0,0 +1,40 @@ +module IPVM.Formats where + +-- Human spec -- + +data Career = Career + { version :: SemVer + , requestor :: DID + , nonce :: Word64 + , config :: Config -- FIXME + , label :: Text + , start :: NowOrLater + , globalConfig :: GlobalConfig + , verification :: VerificationConfig + , jobs :: Map Text Job + } + +data GlobalConfig = GlobalConfig + { wasmConfig :: WasmConfig + , effectConfig :: EffectConfig + , publishResults :: Bool + , auth :: [UCAN] -- FIXME move to post-negotaited? + } + +data Ref + = Absolute CID + | Relative { label :: Text, index :: Word8 } + +data Job = Job + { call :: Call + , jobConfig :: JobConfig + } + +data JobConfig + +data Call + = Effect { object :: Text, action :: Text, timeoutSecs :: Maybe Word64 } + | Wasm { wasm :: WasmBlob, input :: Map Text Ref, gas :: Word64, verification :: Verification } + + +-- Runner spec -- diff --git a/job/job.var1.json b/job/job.var1.json index 51329ad..b12787a 100644 --- a/job/job.var1.json +++ b/job/job.var1.json @@ -27,9 +27,10 @@ } } }, - "start": { + "kickoff": { "run": { - "wasm": "bafyWasm", + "type": "wasm/1.0", + "cid": "bafyWasm", "input": [ { "w": "Qm123456" }, { "x": "Qmabcdef" }, @@ -38,25 +39,44 @@ ], "affinities": [], // TODO lives here, or in }, - "after": { - "effects": { - "bell": { - "run": "system/bell", - "when": "always" - } + "after": [ + { + "when": "always", + "do": "bell" + } + ] + }, + "bell": { + "run": { + "effect": "stdlib/system/bell", + "do": "bell/ring" + } + }, + "afterKickoff": { + "effects": { + "bell": { + "run": "system/bell", + "when": {"after": "kickoff", "scenario": "always"} + "input": "kickoff/_" } } }, "left": { - "run": { - "wasm": "bafyWasmLeft", - "input": ["bafySomeArg"] + "atomically": { + "sub1": { + "wasm": "bafyWasmLeftSub1", + "input": ["bafySomeArg1"] + }, + "sub2": { + "wasm": "bafyWasmLeftSub2", + "input": ["bafySomeArg2"] + } } }, "subleft": { "run": { "wasm": "bafyWasmSubLeft", - "input": [101, {"from": "left"}, 42] + "input": [101, {"from": "left/sub1"}, 42] } }, "right": { @@ -83,3 +103,10 @@ "accepter": "", "job": "Qmabcdef" } + +{ + "type": "ipvm/invocation", + "version": "0.0.1", + "wasm": "Qm12345", + "inputs": ["Qmabcdef", "bafyghijk"] +} From ec8b020b04dafe7306551bcf5e2f0e923cb4013d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 14 Nov 2022 21:48:52 -0800 Subject: [PATCH 07/42] Slowly discovering the shape of the spec --- job/README.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/job/README.md b/job/README.md index 3aa1e43..9b5caa7 100644 --- a/job/README.md +++ b/job/README.md @@ -27,10 +27,27 @@ IPVM provides a deterministic-by-default, content addressed execution environmen > Every application has an inherent amount of irreducible complexity. The only question is: Who will have to deal with it — the user, the application developer, or the platform developer? > -- [Tesler's Law](https://en.wikipedia.org/wiki/Law_of_conservation_of_complexity) -With "jobs" as the unit of execution, programmers gain flexible cache granularity, parallelism, and ___. Partial failure is Transactionality +With "jobs" as the unit of execution, programmers gain flexible cache granularity, parallelism, and ___. -* [Convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration). +Configuration DSLs like IPVM jobs can become very complex. By their nature, jobs specs are responsible for describing as many +By having to account for a huge number of possible cases, the burden is placed on the programmer in exchange for a high degree of control. Sensible defaults, [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration), and scoped settingshelp aleviate this problem. + +Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that update the real world nonmonotonically. + + +### 1.1.1 Nonmonotonic Effects + + +# 2 Anatomy + +## 2.1 Job + +An IPVM job MUST be composed of the following parts: + +* + +## 2.2 # 2 Dataflow Graph @@ -44,6 +61,21 @@ With "jobs" as the unit of execution, programmers gain flexible cache granularit * Spec format IPLD * Input addressing +## 2.2 Implicit Parallelism + +IPVM does not allow programmer control over parallelism. The resources available to the scheulder MAY be very different from run to run. + +The concurrency plan MUST be derived from the dataflow dependencies. + + +# 3 Higher Abstractions + +At the lowest level, IPVM jobs only describe the loading of immutible data. + +* Actors +* Vats +* Map/reduce + # 3 Acknowledgments * [Quinn Wilton](https://github.com/QuinnWilton), Fission From 4c70aab030951e5012a200e64b126afce30f9b31 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 14 Nov 2022 22:43:47 -0800 Subject: [PATCH 08/42] Simplifying! --- job/README.md | 90 +++++++++++++++++++++++++++++++++++++++------ job/job.var1.json | 94 ++++++++++++++++++----------------------------- 2 files changed, 114 insertions(+), 70 deletions(-) diff --git a/job/README.md b/job/README.md index 9b5caa7..07a3661 100644 --- a/job/README.md +++ b/job/README.md @@ -33,23 +33,95 @@ Configuration DSLs like IPVM jobs can become very complex. By their nature, jobs By having to account for a huge number of possible cases, the burden is placed on the programmer in exchange for a high degree of control. Sensible defaults, [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration), and scoped settingshelp aleviate this problem. -Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that update the real world nonmonotonically. +Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that destructively update the real world. +# 2 Effect System -### 1.1.1 Nonmonotonic Effects +## 2.1 Pure Functions +## 2.2 Nondestructive Effects -# 2 Anatomy +## 2.3 Destructive Effects -## 2.1 Job +# 3 Job Anatomy An IPVM job MUST be composed of the following parts: -* +* Header +* Jobs +* Signature + +## 3.1 Header + +## 3.2 Jobs + +The `jobs` field MUST describe a series of jobs that are expected to run in the session. Jobs MUST be one of the following: + +1. A pure computation described by pure (content-addressed) inputs to a Wasm binary +2. A named effect with pure (content-addressed) inputs to be executed by the runtime +3. One of the above, with an input that is the result of a previous step + + +### 3.2.1 Web Assembly Job + +``` json +{ + "type": "wasm/1.0", + "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", + "input": [ + { "firstName": "Boris" }, + { "lastName": "Mann" } + ], + "maxGas": 4600, + "on": { + "error": [], + "success": [] + } +} +``` + +### 3.2.2 Effect Job + +### 3.2.3 Pipelining + +Each job MUST be labelled with a string. This label MUST be treated as local to the enclosing workflow. Jobs MAY reference each other's output by label in the `from` field. In the case of multiple return values, the index of the output may be further selected with the `out` field. For exammple: + +```json +{ + "fullName": { + "type": "wasm/1.0", + "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", + "input": [ + { "firstName": "Boris" }, + { "lastName": "Mann" } + ] + }, + "count": { + "type": "wasm/1.0", + "with": "bafkreiegbnixdoqsohfz5oninnhpcpwsf7rg6ewnx2lvhp7p5axejrph64", + "input": [ + { "name": {"from": "fullName", "out": 0 } } + ] + } +} +``` + +The above is roughly equivalent to the (local) function call: + +```js +fullName({firstName: "Boris", lastName: "Mann"})[0].count() +``` + + + +All resulting graphs MUST be acyclic. The parser MUST check for any cycles and fail immeditely. + + + + + -## 2.2 -# 2 Dataflow Graph * Automatic (and deterministic) parallelism * Dataflow / job graph @@ -99,8 +171,4 @@ At the lowest level, IPVM jobs only describe the loading of immutible data. * [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) - - - - https://www.tweag.io/blog/2020-09-10-nix-cas/ diff --git a/job/job.var1.json b/job/job.var1.json index b12787a..cc915f0 100644 --- a/job/job.var1.json +++ b/job/job.var1.json @@ -1,5 +1,5 @@ { - "type": "ipvm/job", + "type": "ipvm/workflow", "version": "0.0.1", "requestor": "did:key:zAlice", "nonce": "ABCDEF", @@ -19,79 +19,55 @@ }, "jobs": { "database": { - "run": { - "effect": { - "with": "dnslink://example.com", - "do": "dnslink/resolve", - "timeout": "30s" - } - } + "type": "effect", + "with": "dnslink://example.com", + "do": "dnslink/resolve", + "timeout": "30s" }, "kickoff": { - "run": { - "type": "wasm/1.0", - "cid": "bafyWasm", - "input": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "database", "out": 0 } } // "Let the dataflow through you" - { "z": "QmFooBar" }, - ], - "affinities": [], // TODO lives here, or in - }, - "after": [ - { - "when": "always", - "do": "bell" - } - ] + "type": "wasm/1.0", + "with": "bafyWasm", + "input": [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "from": "database", "out": 0 } } // "Let the dataflow through you" + { "z": "QmFooBar" }, + ], + "affinities": [], // TODO lives here, or in + "always": ["bell"] }, "bell": { - "run": { - "effect": "stdlib/system/bell", - "do": "bell/ring" - } + "effect": "stdlib/system/bell", + "do": "bell/ring" }, "afterKickoff": { - "effects": { - "bell": { - "run": "system/bell", - "when": {"after": "kickoff", "scenario": "always"} - "input": "kickoff/_" - } - } + "run": "system/bell", + "when": {"after": "kickoff", "scenario": "always"} + "input": "kickoff/_" }, "left": { - "atomically": { - "sub1": { - "wasm": "bafyWasmLeftSub1", - "input": ["bafySomeArg1"] - }, - "sub2": { - "wasm": "bafyWasmLeftSub2", - "input": ["bafySomeArg2"] - } + "sub1": { + "wasm": "bafyWasmLeftSub1", + "input": ["bafySomeArg1"] + }, + "sub2": { + "wasm": "bafyWasmLeftSub2", + "input": ["bafySomeArg2"] } }, "subleft": { - "run": { - "wasm": "bafyWasmSubLeft", - "input": [101, {"from": "left/sub1"}, 42] - } + "wasm": "bafyWasmSubLeft", + "input": [101, {"from": "left/sub1"}, 42] }, "right": { - "run": { - "wasm": "bafyWasmRight", - "input": [{"from": "start", "output": 4}, {"from": "database"}] - } + "wasm": "bafyWasmRight", + "input": [{"from": "start", "output": 4}, {"from": "database"}] }, "end": { - "run": { - "wasm": "bafyWasmEnd", - "input": { - "foo": "jobs/left/subleft/" - } - } + "wasm": "bafyWasmEnd", + "input": [ + {"foo": "jobs/left/subleft/"} + ] } } "signature": "abcdef" From 2dbdb28abbe87257e7a971b6dba23b2f77208006 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 18 Nov 2022 22:34:03 -0800 Subject: [PATCH 09/42] Formalizing --- README.md | 273 +++++++++++++++++++++---------------------------- job/README.md | 237 ++++++++++++++++++++++-------------------- meta/README.md | 217 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 461 insertions(+), 266 deletions(-) create mode 100644 meta/README.md diff --git a/README.md b/README.md index e772086..a23e50f 100644 --- a/README.md +++ b/README.md @@ -1,217 +1,176 @@ -# IPVM +# Interplanetary Virtual Machine (IPVM) Spec v0.1.0 -This document currently describes the high-level project goals. +## Editors -# 🤹‍♀️ Separate Projects +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) -1. Autocodec -2. wasm-ipfs -3. IPVM +## Authors -These are related, but separate. The relatedness comes from the fact that if we have one Wasm engine in IPFS, then the other components can rely on it too. +* [Blaine Cook](https://github.com/blaine), [Fission](https://fission.codes) +* [Zeeshan Lakhani](https://github.com/zeeshanlakhani), [Fission](https://fission.codes) +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) + +## Language -# :no_good_woman: Antigoals: What An IPVM Is Not +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). -* A replacement of IPFS internals — that's wasm-ipfs -* A language for distributed applications -* A WASI interface -* A new blockchain +# 0 Abstract -# What Is An IPVM? +IPVM -To date, IPFS has been a project focused on data. IPVM is the codename for an attempt to bring execution to IPFS. To this end, IPVM aims to be the easiest, fastest, most secure, and open way to run Wasm anywhere. +An IPVM "job" is a declarative description of WebAssembly and managed effects to be run by the IPVM runtime. -Another way to describe IPVM would be "an open, decentralized, and local-first competitor to AWS Lambda". +# 1 Motivation -The project leverages Wasm, content addressing, SPKI, and object capabilities to liberate computation from depending on specific prenegotiated services like the big cloud providers. Execution should scale flexibly from on-device by default all the way up to edge PoPs and data centers. +IPVM provides a deterministic-by-default, content addressed execution environment. Execution may always be run locally, but there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. -## 📋 High-Level Attributes +## 1.1 Minimizing Complexity -* Wasm-on-IPFS -* Declarative invocation (not an ABI, see section below) -* Captured results, re-ingested to IPFS -* A distributed scheduler - * Single vs cron, local vs remote vs either, etc -* Matchmaking - * Poster/acceptor - * Push-to-remote -* (Global) memoization table & adaptive optimization -* Managed effects (via an IPVM runtime) -* Mobile (ambient) computing, compute-to-data, and data-to-compute -* "The HTTP of Compute" -* Stretch: autonomous agents +> Every application has an inherent amount of irreducible complexity. The only question is: Who will have to deal with it — the user, the application developer, or the platform developer? +> -- [Tesler's Law](https://en.wikipedia.org/wiki/Law_of_conservation_of_complexity) -## :woman_scientist: Research Questions +With "jobs" as the unit of execution, programmers gain flexible cache granularity, parallelism, and ___. -### Pragmatics +Configuration DSLs like IPVM jobs can become very complex. By their nature, jobs specs are responsible for describing as many -Projects like IPVM live and die by balancing power with ease of use. What are the easiest models for users to interact with the system? Clearly BYOL (bring-you-own-language) is a major advantage of Wasm. Are the specific Wasm runtimes that are easier to work with than others? +By having to account for a huge number of possible cases, the burden is placed on the programmer in exchange for a high degree of control. Sensible defaults, [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration), and scoped settingshelp aleviate this problem. -The current hypothesis is "convention over configuration" for defining attributes such as when effects should be run, if subjobs are run concurrently, and so on. One direction is to ask developers to configure a small-but-consistent input and output data structure (e.g. as you often see in middleware). This would open up dataflow analysis and (likely pessimistic) call-by-need semantics ("forcing the effect thunks"), removing the need for complex configuration and strategies from the developer, unless they want to overwrite them. +Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that destructively update the real world. -### Cost semantics +# 2 Effect System -* Can it run locally vs incur network to kick over to a more powerful machine? -* To what degree is it possible to hide this complexity from the programmer? +## 2.1 Pure Functions -### Trust & Verification Model +## 2.2 Nondestructive Effects -* Who can decrypt this data? -* How do I know that this is the right answer? -* Who are bad actors in the system? -Are there opportunities for correct-by-construction code? +## 2.3 Destructive Effects -### Matchmaking & Discovery +# 3 Job Anatomy -* How quickly can a posted job be executed -* Who is equipped to run specific functionality or effects? -* How do we prevent wasted duplicate computation? -* What does a push model look like in this model (e.g. to a specific provider) +An IPVM job MUST be composed of the following parts: -### Effect Systems +* Header +* Jobs +* Signature -* "Don't accidentally send the Tweet twice" -* What's the best way to fit effects into a pure invocation model? -* How can we check that it actually ran? -* Should continuations be modelled as delimited effects? - * If so, perhaps the runtime should have low-ish level `shift` & `reset` calls -* How hard should we lean into defunctionalization? -* Should we support message passing and/or IPC? +## 3.1 Header -### Cooperative Optimization +## 3.2 Jobs -* Do typical compiler and VM techniques like JIT and memoization apply? - * Can we share intermediate results of a computation and store them in a global substrate that anyone can participate in? +The `jobs` field MUST describe a series of jobs that are expected to run in the session. Jobs MUST be one of the following: -## 📃 Declarative Invocation +1. A pure computation described by pure (content-addressed) inputs to a Wasm binary +2. A named effect with pure (content-addressed) inputs to be executed by the runtime +3. One of the above, with an input that is the result of a previous step -The current hypothesis is that invocations can be configured as a declarative description containing at least: -* The CID of the Wasm blob to execute -* The CIDs of the argument -* Any configuration that overrides defaults - * Max gas - * When to run (i.e. cron) - * Associated UCAN or CapTP - * ...and so on... +### 3.2.1 Web Assembly Job -![](./assets/dag-invocation.png) +``` json +{ + "type": "wasm/1.0", + "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", + "input": [ + { "firstName": "Boris" }, + { "lastName": "Mann" } + ], + "maxGas": 4600, + "on": { + "error": [], + "success": [] + } +} +``` -## 🧾 Captured Session / Receipts +### 3.2.2 Effect Job -![](./assets/dag-results.png) +### 3.2.3 Pipelining -The output may include instructions to run further computation (e.g. continuations or other effects). Represented in the diagram below as a dashed line, sending email or enqueuing a new job are handled by the IPVM runtime. [NB: The exact mechanism is not settled, the exact mechanisms are all subject to change] +Each job MUST be labelled with a string. This label MUST be treated as local to the enclosing workflow. Jobs MAY reference each other's output by label in the `from` field. In the case of multiple return values, the index of the output may be further selected with the `out` field. For exammple: -![](./assets/dag-effect.png) +```json +{ + "fullName": { + "type": "wasm/1.0", + "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", + "input": [ + { "firstName": "Boris" }, + { "lastName": "Mann" } + ] + }, + "count": { + "type": "wasm/1.0", + "with": "bafkreiegbnixdoqsohfz5oninnhpcpwsf7rg6ewnx2lvhp7p5axejrph64", + "input": [ + { "name": {"from": "fullName", "out": 0 } } + ] + } +} +``` -## :spiral_calendar: Scheduler +The above is roughly equivalent to the (local) function call: -IPVM needs a way of scheduling computation, signalling made matches, returning control to the job queue, and starting continuations? Linearizability is possibly required in the general case; what's the easiest way to signal weaker consistency? How does the scheduler handle failure of nodes, network partitions, etc? +```js +fullName({firstName: "Boris", lastName: "Mann"})[0].count() +``` -What are the correct default behaviours? Should IPVM computation always operate by (concurrent) graph reduction, or do we need to specify evaluation (and restart) strategies a la Erlang? + -## :handshake: Trust Model +All resulting graphs MUST be acyclic. The parser MUST check for any cycles and fail immeditely. -UCAN & OCAP/CapTP -Execution Metering -* https://github.com/ucan-wg/spec -* https://spritelyproject.org/news/what-is-captp.html -IPVM will often (not always!) execute on remote machines controlled by untrusted third parties. This is potentially precarious for all involved. Some trust is required between participants in all cases. -In offline scenarios, such trust may be provided via SPKI. In live systems, ocap (likely CapTP) should be preferred. -### Open Questions -* Is gas granted directly by capability? (i.e. is gas first-class or an effect?) -* Can SPKI and CapTP interoperate directly? -## :dollar: Payments -Computation is a scarce resource. IPVM is not anti-money; while altruistic computation is _highly encouraged,_ charging for computation is going to quickly become a de facto requirement. The current hypothesis is to bake micropayment capabilities directly into the platform to avoid the immediate capture by prenegotiated providers. +* Automatic (and deterministic) parallelism +* Dataflow / job graph +* Effects System +* Partial Failure & Transactionality +* Auth: SPKI & object capabilities -IPVM aims to not have an "IPVM token" or similar. Prenegotiated providers paid in fiat and metered in credits SHOULD be supported, as should a "spot market" of compute resources on an ad hoc basis. To maximize user choice, this system should be kept _out or on the fringes_ of the IPVM kernel as much as possible. IPVM MUST still allow for running compute yourself, or pushing compute to machines that you or a friendly agent controls "for free". +* Wasm execution in depth +* Spec format IPLD + * Input addressing -* State channels - * ucan-chan (ユーキャンちゃん!) -* Hierarchical consensus and/or Filecoin +## 2.2 Implicit Parallelism -## :rocket: Managed Effects +IPVM does not allow programmer control over parallelism. The resources available to the scheulder MAY be very different from run to run. -Effects are the things that happen outside of pure functions: sending email, retrying a failed execution, reading from a database, playing a bell, firing the missiles. +The concurrency plan MUST be derived from the dataflow dependencies. -Managed effects are handled by the compiler, VM, or runtime. The current hypothesis contains two levels of effect: pure ("platform") effects and impure effects. For completeness, you could say that pure functions are "non-effectful" and also exist on this spectrum of effectfulness. -![](./assets/stream-effects.jpeg) +# 3 Higher Abstractions -Pure effects are ones that "merely" paper over pure functions with helpful abstractions (e.g. implicit state, error handling, continuations). Another way of thinking about them is that they stay "contained" in the system. We can roll back any of these operations, replay them, etc and aside from your CPU generating some extra heat, no one would be the wiser. +At the lowest level, IPVM jobs only describe the loading of immutible data. -Impure effects alter the world itself. If I send an email, I'm unable to reverse that action. +* Actors +* Vats +* Map/reduce -Pure effects are much more convenient to reason about, compose cleanly, and are inspectable. We want to capture as much of an impure effect as possible as pure descriptions. Returning "receipts" from an impure effect can turn a "request for effect" to a pure description of "...and it returned this specific result", which is a pure tuple. Treating it this way allows for idempotence on a stream of effects over time, capture and reuse of intermediate results, and so on. +# 3 Acknowledgments -Impure effects are very powerful, but with great power comes great responsibility... and also often fewer levers for performance optimization. One example is that impure effects often need exactly-once semantics, which requires gaining a global lock on the job (individual effects may be run in parallel, but need to be the only execution for that specific effect). This requires consensus, sometimes even global distributed consensus, which is always slower than being able to work from a distributed queue. +* [Quinn Wilton](https://github.com/QuinnWilton), Fission +* [Eric Myhre](https://github.com/warpfork), Protocol Labs +* [Luke Marsden](https://github.com/lukemarsden), Protocol Labs +* [David Aronchick](https://www.davidaronchick.com/), Protocol Labs +* [Irakli Gozalishvili](https://github.com/Gozala), DAG House +* [Hugo Dias](https://github.com/hugomrdias), DAG House +* [Mikeal Rogers](https://github.com/mikeal/), DAG House +* [Juan Benet](https://github.com/jbenet/), Protocol Labs +* [Christine Lemmer-Webber](https://github.com/cwebber), Spiritely Institute +* [Mark Miller](https://github.com/erights), Agoric +* [Peter Alvaro](https://github.com/palvaro), UC Santa Cruz +* [Joe Hellerstein](https://github.com/jhellerstein), UC Berkley -In general, the number of hosts that can provide a particular effect are smaller than the hosts that can compute pure functions. There may simply be a tradeoff for the programmer to say "yes I really need this effect, even though I'll have fewer options". +# 4 Prior Art -### What About WASI? +* [Docker Job Controller](https://kubernetes.io/docs/concepts/workloads/controllers/job/) +* BucketVM (UCAN Invocation) +* [WarpForge "Formula" v1](https://github.com/warpfork/warpforge/blob/master/examples/110-formula-usage/example-formula-exec.md) +* [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) -We will almost certainly need to enable WASI at some stage. This is much more complex, as service discovery becomes a larger, and much more nuanced problem as arbitrary effects can be very difficult to make safe and deterministic. For example, what if the executable fills your disk with garbage? -Should the core IPVM runtime provide "blessed" effects that operate over the shared memory interface? For example, I see no problem with providing a source of randomness as an external effect because it's useful, and probably safe. It "just" needs to come from "outside" the computation. These can even be captured in the trace. - -## :telescope: Sources of Inspiration - -We can learn a lot from adjacent projects. Some of these include: - -* WASI -* FVM -* Bacalhau -* BucketVM -* IPFS-FAN -* Bloom^L -* PACT/HydroLogic -* Nix -* Dialog - -## :world_map: Roadmap - -### A. Learning Phase - -1. Bash-script store/load/run Wasm from IPFS -1. Memoization table - * Local - * Remote, incl demo "look how fast it is on a remote machine now" -1. Bash-script module pipelining, capturing intermediate results -1. Experimentation with ABI (C conventions?) -1. Pure effects (e.g. atomic FS or DB read/write) -1. Initial attempt to run adaptive optimization on common partial applications - -### B. Specs - -* IPLI & session receipts -* Scheduler & matchmaking -* Memoized result format & lookup -* Capability model - * Offline (SPKI) - * Online (ocap/CapTP) -* Verification mechanisms -* Compute on encrypted data (trusted & FHE) - -# FAQ - -## How Does This Differ From Autocodec and wasm-ipfs? - -Autocodec, IPVM, and wasm-ipfs all involve Wasm and IPFS. They are distinct projects, though sharing modules and learning between them is a nice-to-have. Having a Wasm interpreter in every IPFS node makes the argument for all of these projects much easier. - -wasm-ipfs is the replacement of IPFS internals with IPFS, to help share high-quality components across implementations and platforms. - -Autocodec is an attempt to replace in-built IPFS codecs with an ad hoc mechanism at read-time. The basic idea is "what if the codec executable was wrapped directly around the IPLD to interpret?" - -IPVM is a distributed execution engine, scheduler, service discovery layer / matchmaking, and memoization system. It is possibly the largest of the three projects. If wasm-ipfs is "IPFS _as_ Wasm", then IPVM is "Wasm _on_ IPFS" - -## Resources - -https://www.youtube.com/watch?v=rzJWk1nlYvs +https://www.tweag.io/blog/2020-09-10-nix-cas/ diff --git a/job/README.md b/job/README.md index 07a3661..8bdb2cc 100644 --- a/job/README.md +++ b/job/README.md @@ -1,4 +1,4 @@ -# IPVM Job Spec +# IPVM Job Configuration Spec v0.1.0 ## Editors @@ -6,169 +6,188 @@ ## Authors -* [Blaine Cook](https://github.com/blaine), [Fission](https://fission.codes) -* [Zeeshan Lakhani](https://github.com/zeeshanlakhani), [Fission](https://fission.codes) * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) - + + ## Language The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). # 0 Abstract -An IPVM "job" is a declarative description of WebAssembly and managed effects to be run by the IPVM runtime. +The IPVM job configuration defines the global parameters for a proposed job, the dependenceis between steps, the distrinction between # 1 Motivation -IPVM provides a deterministic-by-default, content addressed execution environment. Execution may always be run locally, but there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. - -## 1.1 Minimizing Complexity - -> Every application has an inherent amount of irreducible complexity. The only question is: Who will have to deal with it — the user, the application developer, or the platform developer? -> -- [Tesler's Law](https://en.wikipedia.org/wiki/Law_of_conservation_of_complexity) - -With "jobs" as the unit of execution, programmers gain flexible cache granularity, parallelism, and ___. - -Configuration DSLs like IPVM jobs can become very complex. By their nature, jobs specs are responsible for describing as many +> 8. A programming language is low level when its programs require attention to the irrelevant. +> Alan Perlis, Epigrams on Programming -By having to account for a huge number of possible cases, the burden is placed on the programmer in exchange for a high degree of control. Sensible defaults, [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration), and scoped settingshelp aleviate this problem. +Any programming langauge must include invocation. The IPVM Job configuration DSL provides a configuration wrapper for ___. -Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that destructively update the real world. +A declarative invocation liberates the programmer from worrying about sequencing, parallelism, distribution, error handling, and _______. Such a specification also grants power to the runtime (and especially the distributed scheduler) to coordinate the running of -# 2 Effect System +The aim of this specification is to allow the configuration of jobs with -## 2.1 Pure Functions -## 2.2 Nondestructive Effects -## 2.3 Destructive Effects -# 3 Job Anatomy +https://www.ams.org/journals/tran/1936-039-03/S0002-9947-1936-1501858-0/S0002-9947-1936-1501858-0.pdf -An IPVM job MUST be composed of the following parts: +confluence -* Header -* Jobs -* Signature +# 2 Task -## 3.1 Header +While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. The ordering MUST be implied from the inputs, flowing source to sink. -## 3.2 Jobs +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. -The `jobs` field MUST describe a series of jobs that are expected to run in the session. Jobs MUST be one of the following: +| Field | Type | Description | Required | +|-----------|---------------------|----------------------------------|----------| +| `type` | `string` | The type of task (Wasm, etc) | Yes | +| `with` | `CID` | Reference to the Wasm to run | Yes | +| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | +| `always` | `[Label]` | An | No | +| `onError` | `[Label]` | An | No | -1. A pure computation described by pure (content-addressed) inputs to a Wasm binary -2. A named effect with pure (content-addressed) inputs to be executed by the runtime -3. One of the above, with an input that is the result of a previous step - - -### 3.2.1 Web Assembly Job + ``` json { - "type": "wasm/1.0", - "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", - "input": [ - { "firstName": "Boris" }, - { "lastName": "Mann" } - ], - "maxGas": 4600, - "on": { - "error": [], - "success": [] - } + errA: { + type: "ipvm/wasm", + effect: + } } ``` -### 3.2.2 Effect Job +## 2.1 Input -### 3.2.3 Pipelining +# 3 Pure Wasm -Each job MUST be labelled with a string. This label MUST be treated as local to the enclosing workflow. Jobs MAY reference each other's output by label in the `from` field. In the case of multiple return values, the index of the output may be further selected with the `out` field. For exammple: +When treated as a black box, the deterministic subset of Wasm may be treated as a pure function. -```json -{ - "fullName": { - "type": "wasm/1.0", - "with": "bafkreie53mk3duiynh5pzmhuzadaif6hpizod5wr6dt34canmxo7j7jfcu", - "input": [ - { "firstName": "Boris" }, - { "lastName": "Mann" } - ] - }, - "count": { - "type": "wasm/1.0", - "with": "bafkreiegbnixdoqsohfz5oninnhpcpwsf7rg6ewnx2lvhp7p5axejrph64", - "input": [ - { "name": {"from": "fullName", "out": 0 } } - ] - } -} -``` +The Wasm configuration MUST extend the core task type with the following fields: -The above is roughly equivalent to the (local) function call: +| Field | Type | Description | Required | Default | +|----------|---------------------|----------------------------------|----------|-------------------------------| +| `type` | `"ipvm/wasm/1.0"` | Identify this task as Wasm 1.0 | Yes | | +| `with` | CID | Reference to the Wasm to run | Yes | | +| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `maxGas` | Integer | | No | 1000 | -```js -fullName({firstName: "Boris", lastName: "Mann"})[0].count() -``` +# 4 Effects - +The `with` field MAY be filled from a relative value (previous step) -All resulting graphs MUST be acyclic. The parser MUST check for any cycles and fail immeditely. + +## 4.1 DNS +| Field | Type | Description | Required | +|---------|---------------------|-----------------------------------------------------------------------|-------------| +| `type` | `"ipvm/effect/dns"` | Identify this job as a Wasm 1.0 | Yes | +| `with` | URI | DNS URI (domain name or subdomain) | Yes | +| `do` | crud | Any ability in the `crud` namespace (e.g. `crud/read`, `crud/update`) | Yes | +| `value` | String | | On mutation | +More specific uses MAY be built out of the primitive DNS resolver. + +### 4.1.1 Examples +Read from [DNSLink](https://dnslink.io) +``` json +{ + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" +} +``` -* Automatic (and deterministic) parallelism -* Dataflow / job graph -* Effects System -* Partial Failure & Transactionality -* Auth: SPKI & object capabilities +Update an A record -* Wasm execution in depth -* Spec format IPLD - * Input addressing +``` json +{ + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=A", + "do": "crud/update", + "value": "12.345.67.890" +} +``` + +[did:dns](https://danubetech.github.io/did-method-dns/) + +``` json +{ + "type": "ipvm/effect", + "with": "dns://_key1._did.example.com?TYPE=URI", + "do": "crud/read" +} +``` + +## 4.2 Bacalhau -## 2.2 Implicit Parallelism -IPVM does not allow programmer control over parallelism. The resources available to the scheulder MAY be very different from run to run. +# 5 Exception Handling -The concurrency plan MUST be derived from the dataflow dependencies. +Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. +It is often desirable to fire a specific job in the case that a job fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. -# 3 Higher Abstractions +Each task MAY include a failure job to run on failure. -At the lowest level, IPVM jobs only describe the loading of immutible data. +Note that effectful exception handlers that depend on specific capabilities (such as network access) MAY fail for the same reason as the job that caused the exception to be thrown. Running a pure effect is RECOMMENDED. -* Actors -* Vats -* Map/reduce -# 3 Acknowledgments -* [Quinn Wilton](https://github.com/QuinnWilton), Fission -* [Eric Myhre](https://github.com/warpfork), Protocol Labs -* [Luke Marsden](https://github.com/lukemarsden), Protocol Labs -* [David Aronchick](https://www.davidaronchick.com/), Protocol Labs -* [Irakli Gozalishvili](https://github.com/Gozala), DAG House -* [Hugo Dias](https://github.com/hugomrdias), DAG House -* [Mikeal Rogers](https://github.com/mikeal/), DAG House -* [Juan Benet](https://github.com/jbenet/), Protocol Labs -* [Christine Lemmer-Webber](https://github.com/cwebber), Spiritely Institute -* [Mark Miller](https://github.com/erights), Agoric -* [Peter Alvaro](https://github.com/palvaro), UC Santa Cruz -* [Joe Hellerstein](https://github.com/jhellerstein), UC Berkley +# 6 Task Scheduler -# 4 Prior Art + -* [Docker Job Controller](https://kubernetes.io/docs/concepts/workloads/controllers/job/) -* BucketVM (UCAN Invocation) -* [WarpForge "Formula" v1](https://github.com/warpfork/warpforge/blob/master/examples/110-formula-usage/example-formula-exec.md) -* [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) +# 7 Container +The outer wrapper of a job contains the -https://www.tweag.io/blog/2020-09-10-nix-cas/ +| Field | Type | Description | Required | +|-------------|--------------------|-------------------------------|----------| +| `type` | `"ipvm/job"` | Object type identifier | Yes | +| `version` | `"0.1.0"` | IPVM job version | Yes | +| `requestor` | DID | Requestor's DID | Yes | +| `run` | `{String => Task}` | Individual named tasks | Yes | +| `signature` | Varsig | Signature of all other fields | Yes | + +``` json +{ + type: "ipvm/job" + version: "0.1.0" + reuqestor: "did:key:zAlice", + nonce: "xjd72gs_k", + run: { + start: { + type: "ipvm/effect", + with: "", + do: "ipvm/dnslink/resolve" + }, + left: { + type: "ipvm/wasm", + with: myWasm, + inputs: [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "from": "database", "out": 0 } } + { "z": "QmFooBar" }, + ] + }, + right: { + type: "ipvm/wasm", + }, + end: { + type: "ipvm/wasm", + + } + }, + signature: 10100010010011 +} +``` diff --git a/meta/README.md b/meta/README.md new file mode 100644 index 0000000..e772086 --- /dev/null +++ b/meta/README.md @@ -0,0 +1,217 @@ +# IPVM + +This document currently describes the high-level project goals. + +# 🤹‍♀️ Separate Projects + +1. Autocodec +2. wasm-ipfs +3. IPVM + +These are related, but separate. The relatedness comes from the fact that if we have one Wasm engine in IPFS, then the other components can rely on it too. + +# :no_good_woman: Antigoals: What An IPVM Is Not + +* A replacement of IPFS internals — that's wasm-ipfs +* A language for distributed applications +* A WASI interface +* A new blockchain + +# What Is An IPVM? + +To date, IPFS has been a project focused on data. IPVM is the codename for an attempt to bring execution to IPFS. To this end, IPVM aims to be the easiest, fastest, most secure, and open way to run Wasm anywhere. + +Another way to describe IPVM would be "an open, decentralized, and local-first competitor to AWS Lambda". + +The project leverages Wasm, content addressing, SPKI, and object capabilities to liberate computation from depending on specific prenegotiated services like the big cloud providers. Execution should scale flexibly from on-device by default all the way up to edge PoPs and data centers. + +## 📋 High-Level Attributes + +* Wasm-on-IPFS +* Declarative invocation (not an ABI, see section below) +* Captured results, re-ingested to IPFS +* A distributed scheduler + * Single vs cron, local vs remote vs either, etc +* Matchmaking + * Poster/acceptor + * Push-to-remote +* (Global) memoization table & adaptive optimization +* Managed effects (via an IPVM runtime) +* Mobile (ambient) computing, compute-to-data, and data-to-compute +* "The HTTP of Compute" +* Stretch: autonomous agents + +## :woman_scientist: Research Questions + +### Pragmatics + +Projects like IPVM live and die by balancing power with ease of use. What are the easiest models for users to interact with the system? Clearly BYOL (bring-you-own-language) is a major advantage of Wasm. Are the specific Wasm runtimes that are easier to work with than others? + +The current hypothesis is "convention over configuration" for defining attributes such as when effects should be run, if subjobs are run concurrently, and so on. One direction is to ask developers to configure a small-but-consistent input and output data structure (e.g. as you often see in middleware). This would open up dataflow analysis and (likely pessimistic) call-by-need semantics ("forcing the effect thunks"), removing the need for complex configuration and strategies from the developer, unless they want to overwrite them. + +### Cost semantics + +* Can it run locally vs incur network to kick over to a more powerful machine? +* To what degree is it possible to hide this complexity from the programmer? + +### Trust & Verification Model + +* Who can decrypt this data? +* How do I know that this is the right answer? +* Who are bad actors in the system? +Are there opportunities for correct-by-construction code? + +### Matchmaking & Discovery + +* How quickly can a posted job be executed +* Who is equipped to run specific functionality or effects? +* How do we prevent wasted duplicate computation? +* What does a push model look like in this model (e.g. to a specific provider) + +### Effect Systems + +* "Don't accidentally send the Tweet twice" +* What's the best way to fit effects into a pure invocation model? +* How can we check that it actually ran? +* Should continuations be modelled as delimited effects? + * If so, perhaps the runtime should have low-ish level `shift` & `reset` calls +* How hard should we lean into defunctionalization? +* Should we support message passing and/or IPC? + +### Cooperative Optimization + +* Do typical compiler and VM techniques like JIT and memoization apply? + * Can we share intermediate results of a computation and store them in a global substrate that anyone can participate in? + +## 📃 Declarative Invocation + +The current hypothesis is that invocations can be configured as a declarative description containing at least: + +* The CID of the Wasm blob to execute +* The CIDs of the argument +* Any configuration that overrides defaults + * Max gas + * When to run (i.e. cron) + * Associated UCAN or CapTP + * ...and so on... + +![](./assets/dag-invocation.png) + +## 🧾 Captured Session / Receipts + +![](./assets/dag-results.png) + +The output may include instructions to run further computation (e.g. continuations or other effects). Represented in the diagram below as a dashed line, sending email or enqueuing a new job are handled by the IPVM runtime. [NB: The exact mechanism is not settled, the exact mechanisms are all subject to change] + +![](./assets/dag-effect.png) + +## :spiral_calendar: Scheduler + +IPVM needs a way of scheduling computation, signalling made matches, returning control to the job queue, and starting continuations? Linearizability is possibly required in the general case; what's the easiest way to signal weaker consistency? How does the scheduler handle failure of nodes, network partitions, etc? + +What are the correct default behaviours? Should IPVM computation always operate by (concurrent) graph reduction, or do we need to specify evaluation (and restart) strategies a la Erlang? + +## :handshake: Trust Model + +UCAN & OCAP/CapTP +Execution Metering + +* https://github.com/ucan-wg/spec +* https://spritelyproject.org/news/what-is-captp.html + +IPVM will often (not always!) execute on remote machines controlled by untrusted third parties. This is potentially precarious for all involved. Some trust is required between participants in all cases. + +In offline scenarios, such trust may be provided via SPKI. In live systems, ocap (likely CapTP) should be preferred. + +### Open Questions + +* Is gas granted directly by capability? (i.e. is gas first-class or an effect?) +* Can SPKI and CapTP interoperate directly? + +## :dollar: Payments + +Computation is a scarce resource. IPVM is not anti-money; while altruistic computation is _highly encouraged,_ charging for computation is going to quickly become a de facto requirement. The current hypothesis is to bake micropayment capabilities directly into the platform to avoid the immediate capture by prenegotiated providers. + +IPVM aims to not have an "IPVM token" or similar. Prenegotiated providers paid in fiat and metered in credits SHOULD be supported, as should a "spot market" of compute resources on an ad hoc basis. To maximize user choice, this system should be kept _out or on the fringes_ of the IPVM kernel as much as possible. IPVM MUST still allow for running compute yourself, or pushing compute to machines that you or a friendly agent controls "for free". + +* State channels + * ucan-chan (ユーキャンちゃん!) +* Hierarchical consensus and/or Filecoin + +## :rocket: Managed Effects + +Effects are the things that happen outside of pure functions: sending email, retrying a failed execution, reading from a database, playing a bell, firing the missiles. + +Managed effects are handled by the compiler, VM, or runtime. The current hypothesis contains two levels of effect: pure ("platform") effects and impure effects. For completeness, you could say that pure functions are "non-effectful" and also exist on this spectrum of effectfulness. + +![](./assets/stream-effects.jpeg) + +Pure effects are ones that "merely" paper over pure functions with helpful abstractions (e.g. implicit state, error handling, continuations). Another way of thinking about them is that they stay "contained" in the system. We can roll back any of these operations, replay them, etc and aside from your CPU generating some extra heat, no one would be the wiser. + +Impure effects alter the world itself. If I send an email, I'm unable to reverse that action. + +Pure effects are much more convenient to reason about, compose cleanly, and are inspectable. We want to capture as much of an impure effect as possible as pure descriptions. Returning "receipts" from an impure effect can turn a "request for effect" to a pure description of "...and it returned this specific result", which is a pure tuple. Treating it this way allows for idempotence on a stream of effects over time, capture and reuse of intermediate results, and so on. + +Impure effects are very powerful, but with great power comes great responsibility... and also often fewer levers for performance optimization. One example is that impure effects often need exactly-once semantics, which requires gaining a global lock on the job (individual effects may be run in parallel, but need to be the only execution for that specific effect). This requires consensus, sometimes even global distributed consensus, which is always slower than being able to work from a distributed queue. + +In general, the number of hosts that can provide a particular effect are smaller than the hosts that can compute pure functions. There may simply be a tradeoff for the programmer to say "yes I really need this effect, even though I'll have fewer options". + +### What About WASI? + +We will almost certainly need to enable WASI at some stage. This is much more complex, as service discovery becomes a larger, and much more nuanced problem as arbitrary effects can be very difficult to make safe and deterministic. For example, what if the executable fills your disk with garbage? + +Should the core IPVM runtime provide "blessed" effects that operate over the shared memory interface? For example, I see no problem with providing a source of randomness as an external effect because it's useful, and probably safe. It "just" needs to come from "outside" the computation. These can even be captured in the trace. + +## :telescope: Sources of Inspiration + +We can learn a lot from adjacent projects. Some of these include: + +* WASI +* FVM +* Bacalhau +* BucketVM +* IPFS-FAN +* Bloom^L +* PACT/HydroLogic +* Nix +* Dialog + +## :world_map: Roadmap + +### A. Learning Phase + +1. Bash-script store/load/run Wasm from IPFS +1. Memoization table + * Local + * Remote, incl demo "look how fast it is on a remote machine now" +1. Bash-script module pipelining, capturing intermediate results +1. Experimentation with ABI (C conventions?) +1. Pure effects (e.g. atomic FS or DB read/write) +1. Initial attempt to run adaptive optimization on common partial applications + +### B. Specs + +* IPLI & session receipts +* Scheduler & matchmaking +* Memoized result format & lookup +* Capability model + * Offline (SPKI) + * Online (ocap/CapTP) +* Verification mechanisms +* Compute on encrypted data (trusted & FHE) + +# FAQ + +## How Does This Differ From Autocodec and wasm-ipfs? + +Autocodec, IPVM, and wasm-ipfs all involve Wasm and IPFS. They are distinct projects, though sharing modules and learning between them is a nice-to-have. Having a Wasm interpreter in every IPFS node makes the argument for all of these projects much easier. + +wasm-ipfs is the replacement of IPFS internals with IPFS, to help share high-quality components across implementations and platforms. + +Autocodec is an attempt to replace in-built IPFS codecs with an ad hoc mechanism at read-time. The basic idea is "what if the codec executable was wrapped directly around the IPLD to interpret?" + +IPVM is a distributed execution engine, scheduler, service discovery layer / matchmaking, and memoization system. It is possibly the largest of the three projects. If wasm-ipfs is "IPFS _as_ Wasm", then IPVM is "Wasm _on_ IPFS" + +## Resources + +https://www.youtube.com/watch?v=rzJWk1nlYvs From bd5bde237033d3874644c3a72ff96d510488bfad Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 18 Nov 2022 22:58:25 -0800 Subject: [PATCH 10/42] Start skwtching out WarpForge --- job/README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/job/README.md b/job/README.md index 8bdb2cc..a486d46 100644 --- a/job/README.md +++ b/job/README.md @@ -129,6 +129,44 @@ Update an A record ## 4.2 Bacalhau +## 4.2 WarpForge + + + +``` json +{ + "container": { + "type": "ipvm/effect", + "with": WARPFORGE, + "do": "container/build", + "inputs": { + "rootfs": "abc", + "gawk": "def", + "data": "ghi" + } + }, + "awk-test": { + "type": "ipvm/warpforge", + "with": "container/out", + "do": "script/interpret", + "inputs": { + "interpreter": "bin/sh", + "contents": [ + "mkdir /out", + "/tools ..." + ] + }, + "outputs": { + "out": { + "from": "/out", + "packtype": "tar" + } + } + } +} + +``` + # 5 Exception Handling @@ -178,10 +216,23 @@ The outer wrapper of a job contains the { "x": "Qmabcdef" }, { "y": { "from": "database", "out": 0 } } { "z": "QmFooBar" }, - ] + ], + outputs: ["a", "b"] }, right: { - type: "ipvm/wasm", + type: "ipvm/warpforge", + with: ____, + do: "build", + inputs: { + "rootfs": "abc", + "gawk": "def", + "data": "ghi" + }, + steps: { + awk-test: { + + } + } }, end: { type: "ipvm/wasm", From 35d1bb2298424dcd0e605f4f62ba1b844e2321b1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 19 Nov 2022 22:58:17 -0800 Subject: [PATCH 11/42] Fleshing out effects and intro sections --- README.md | 26 +++- job/README.md | 369 ++++++++++++++++++++++++++++++--------------- job/foo.hs | 40 ----- job/job.json | 135 ++++++++--------- job/job.png | Bin 116818 -> 0 bytes job/warpforge.json | 36 +++++ 6 files changed, 363 insertions(+), 243 deletions(-) delete mode 100644 job/foo.hs delete mode 100644 job/job.png create mode 100644 job/warpforge.json diff --git a/README.md b/README.md index a23e50f..cd44103 100644 --- a/README.md +++ b/README.md @@ -152,18 +152,19 @@ At the lowest level, IPVM jobs only describe the loading of immutible data. # 3 Acknowledgments +* [Joe Armstrong](https://joearms.github.io/), Ericsson +* [Mark Miller](https://github.com/erights), Agoric +* [Peter Alvaro](https://github.com/palvaro), UC Santa Cruz +* [Joe Hellerstein](https://github.com/jhellerstein), UC Berkley +* [Juan Benet](https://github.com/jbenet/), Protocol Labs +* [Christine Lemmer-Webber](https://github.com/cwebber), Spiritely Institute * [Quinn Wilton](https://github.com/QuinnWilton), Fission -* [Eric Myhre](https://github.com/warpfork), Protocol Labs * [Luke Marsden](https://github.com/lukemarsden), Protocol Labs * [David Aronchick](https://www.davidaronchick.com/), Protocol Labs +* [Eric Myhre](https://github.com/warpfork), Protocol Labs * [Irakli Gozalishvili](https://github.com/Gozala), DAG House * [Hugo Dias](https://github.com/hugomrdias), DAG House * [Mikeal Rogers](https://github.com/mikeal/), DAG House -* [Juan Benet](https://github.com/jbenet/), Protocol Labs -* [Christine Lemmer-Webber](https://github.com/cwebber), Spiritely Institute -* [Mark Miller](https://github.com/erights), Agoric -* [Peter Alvaro](https://github.com/palvaro), UC Santa Cruz -* [Joe Hellerstein](https://github.com/jhellerstein), UC Berkley # 4 Prior Art @@ -173,4 +174,17 @@ At the lowest level, IPVM jobs only describe the loading of immutible data. * [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) +# FIXME STASH + https://www.tweag.io/blog/2020-09-10-nix-cas/ + +https://www.ams.org/journals/tran/1936-039-03/S0002-9947-1936-1501858-0/S0002-9947-1936-1501858-0.pdf + +* confluence +* differential dataflow +* map/reduce +* actors & loops +* captp/ocapn +* Enqueuing new jobs in output +IPVM implements a capability model based on keys, linked certificates, and CapTP. Executor certificate negotiation MUST happen during negotiation, + diff --git a/job/README.md b/job/README.md index a486d46..18dd143 100644 --- a/job/README.md +++ b/job/README.md @@ -1,4 +1,4 @@ -# IPVM Job Configuration Spec v0.1.0 +# IPVM Job Spec v0.1.0 ## Editors @@ -17,55 +17,214 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S The IPVM job configuration defines the global parameters for a proposed job, the dependenceis between steps, the distrinction between -# 1 Motivation +Any computing environment by defintion must include invocation. The IPVM Job configuration DSL provides configuration and dataflow for pure computation and effects. + +# 1 Introduction + +## 1.1 Motivation + +## 1.2 Design Philosophy > 8. A programming language is low level when its programs require attention to the irrelevant. -> Alan Perlis, Epigrams on Programming +> +> — Alan Perlis, Epigrams on Programming -Any programming langauge must include invocation. The IPVM Job configuration DSL provides a configuration wrapper for ___. +While IPVM MAY execute arbitrary programs, IPVM Jobs are specified declaratively. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, and distribution. Such a specification also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. -A declarative invocation liberates the programmer from worrying about sequencing, parallelism, distribution, error handling, and _______. Such a specification also grants power to the runtime (and especially the distributed scheduler) to coordinate the running of +On the other hand, IPVM Jobs are support few features and impose few constraints on what can be run. There is no first-class concept of persistent objects or loops. This layer of IPVM is only concerned with terminating execution. Loops, actors, vats, and so on MAY be implemented on top of IPVM Jobs by enqueuing new jobs using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). -The aim of this specification is to allow the configuration of jobs with +The core restrictions enforced by the design of IPVM Jobs are: +1. Execution MUST terminate in finite time +2. Job tasks MUST form a partial order +3. Effects MUST be managed by the runtime and declared ahead of time +While effects MUST be declared up front, they MAY also be emitted as output from pure computation (see the core spec for more). This provides a "legal" escape hatch for building higher-level abstraction that incorporate effects. +## 1.2 Humane Design -https://www.ams.org/journals/tran/1936-039-03/S0002-9947-1936-1501858-0/S0002-9947-1936-1501858-0.pdf +> People are part of the system. The design should match the user's experience, expectations, and mental models. +> +> — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design -confluence +While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The target is moving jobs and tasks between machines, logging, and debugging. As such IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. -# 2 Task +## 1.3 Security Considerations -While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. The ordering MUST be implied from the inputs, flowing source to sink. +Working with encrypted data and application secrets (section X.Y) is common practice for many jobs. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Job. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and never memoized. -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. +# 2 Container + +The outer wrapper of a job contains the + +configuration vs content layers + +| Field | Type | Description | Required | Default | +|-------------|--------------------|--------------------------------|----------|---------| +| `type` | `"ipvm/job"` | Object type identifier | Yes | | +| `version` | `"0.1.0"` | IPVM job version | Yes | | +| `requestor` | DID | Requestor's DID | Yes | | +| `nonce` | String | | Yes | | +| `parent` | `CID | null` | | No | `null` | +| `meta` | Object | | No | `{}` | +| `config` | `IpvmConfig` | | No | | +| `run` | `{String => Task}` | Named tasks | Yes | | +| `signature` | Varsig | Signature of serialized fields | Yes | | + +## 2.1 `type` Field + +The `type` field MUST be set to `ipvm/job`. + +...FIXME more text... + +## 2.2 `version` -| Field | Type | Description | Required | -|-----------|---------------------|----------------------------------|----------| -| `type` | `string` | The type of task (Wasm, etc) | Yes | -| `with` | `CID` | Reference to the Wasm to run | Yes | -| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | -| `always` | `[Label]` | An | No | -| `onError` | `[Label]` | An | No | +## 2.X Examples - +### 2.X.1 Pure + +Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). ``` json { - errA: { - type: "ipvm/wasm", - effect: - } + "type": "ipvm/job", + "version": "0.1.0", + "requestor": "did:key:zAlice", + "nonce": "o3--8Gdu5", + "run": { + "left": { + "type": "ipvm/wasm", // or make this a label for the microkernel? + "kernel: "Qm12345", // Or here? + "with": leftWasm, + "inputs": [], + "outputs": ["a", "b"] + }, + "right": { + "type": "ipvm/wasm", + "with": "rightWasm", + "inputs": [ + { "bar": "bafy123" } + ], + "outputs": ["a", "b"] + }, + "end": { + "type": "ipvm/wasm", + "with": "QmEndWasm", + "inputs": [ + { "a": { "from": "left" } }, + { "b": { "from": "right" } }, + { "c": 123 } + ] + } + } + "signature": "abcdef" } ``` -## 2.1 Input +## 2.X.2 Effectful -# 3 Pure Wasm +Here is an example of a nontrivial IPVM job which reads from DNS, performs several jobs on the value, and atomically performs a DNS update with the output value. -When treated as a black box, the deterministic subset of Wasm may be treated as a pure function. +``` json +{ + "type": "ipvm/job", + "version": "0.1.0", + "requestor": "did:key:zAlice", + "nonce": "xjd72gs_k", + "run": { + "read-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" + }, + "check-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" + "inputs": [ + { "_": { "from": "end" } } + ] + }, + "write-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/update", + "inputs": [ + { "value": { "from": "end" } } + { "_": { "from": "cas" } } + ], + + } + "left": { + "type": "ipvm/wasm", // or make this a label for the microkernel? + "kernel: "Qm12345", // Or here? + "with": leftWasm, + "inputs": [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "from": "read-dns" } } + { "z": "QmFooBar" }, + ], + "outputs": ["a", "b"] + }, + "right": { + "type": "ipvm/wasm", + "with": "rightWasm", + "inputs": [ + { "foo": { "from": "read-dns/out" } }, + { "bar": "bafy123" } + ], + "outputs": ["a", "b"] + }, + "end": { + "type": "ipvm/wasm", + "with": "QmEndWasm", + "inputs": [ + { "a": { "from": "left" } }, + { "b": { "from": "right" } }, + { "c": 123 } + ] + }, + "cas": { + "type": "ipvm/wasm", + "with": "cafyCasWasm", + "inputs": [ + { "initial": "read-dns" }, + { "latest": "check-dns" } + ] + } + }, + "exception": { + "format-message": { + type: "ipvm/wasm", + with: handlerWasm + } + }, + "signature": "abcdef" +} +``` + +# 3 Task + +While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. The ordering MUST be implied from the inputs, flowing source to sink. + +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. + +| Field | Type | Description | Required | Default | +|----------|---------------------|----------------------------------|----------|---------| +| `type` | `string` | The type of task (Wasm, etc) | Yes | | +| `with` | `CID` | Reference to the Wasm to run | Yes | | +| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `auth` | `UCAN[]` | | Yes | | +| `secret` | `Boolean` | | No | `True` | +| `meta` | `Object` | | No | `{}` | +## 3.1 Input + +# 4 Pure Wasm + +When treated as a black box, the deterministic subset of Wasm may be treated as a pure function. + The Wasm configuration MUST extend the core task type with the following fields: | Field | Type | Description | Required | Default | @@ -75,13 +234,62 @@ The Wasm configuration MUST extend the core task type with the following fields: | `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | | `maxGas` | Integer | | No | 1000 | -# 4 Effects +# 5 Effects The `with` field MAY be filled from a relative value (previous step) - +## 5.1 Secrets + +Secrets MUST modelled as effects. Just as not every machine will have the ability to update a DNS record, not all will be able to decrypt data or sign data with a specific private key. These effects SHOULD default to private visibility. + +### 5.1.1 Signing + +| Field | Type | Description | Required | Default | +|----------|-----------------|---------------------------------|-------------|---------| +| `type` | `"ipvm/effect"` | Identify this job as a Wasm 1.0 | Yes | | +| `with` | DID | | Yes | | +| `do` | `"crypto/sign"` | | Yes | | +| `value` | String | | On mutation | | +| `public` | `Boolean` | RECOMMENDED not public | Yes | `false` | -## 4.1 DNS + + +``` json +{ + "type": "ipvm/effect", + "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", + "do": "crypto/sign", + "inputs": [{ "value": "aBcDeF" }] +} +``` + +### 5.1.2 Out-of-Band Decryption + +``` json +{ + "type": "ipvm/effect", + "with": "ipns://alice.fission.name/supersecret", + "do": "crypto/decrypt", + "public": false, + "inputs": [{ "value": "aBcDeF" }] +} +``` + +### 5.1.3 In-Band Secrets + +Some cases require having direct access to a secret, such as a + +``` json +{ + "type": "ipvm/effect", + "with": "ipvm:secret:github.com/ipvm-wg/spec?secret=API_KEY_NAME", + "do": "secret/get", + "public": false, + "inputs": [{ "value": "aBcDeF" }] +} +``` + +## 5.2 DNS | Field | Type | Description | Required | |---------|---------------------|-----------------------------------------------------------------------|-------------| @@ -94,8 +302,6 @@ More specific uses MAY be built out of the primitive DNS resolver. -### 4.1.1 Examples - Read from [DNSLink](https://dnslink.io) ``` json @@ -127,48 +333,21 @@ Update an A record } ``` -## 4.2 Bacalhau +## 5.3 IPNS -## 4.2 WarpForge - - +[IPNS](https://docs.ipfs.tech/concepts/ipns/) ``` json { - "container": { - "type": "ipvm/effect", - "with": WARPFORGE, - "do": "container/build", - "inputs": { - "rootfs": "abc", - "gawk": "def", - "data": "ghi" - } - }, - "awk-test": { - "type": "ipvm/warpforge", - "with": "container/out", - "do": "script/interpret", - "inputs": { - "interpreter": "bin/sh", - "contents": [ - "mkdir /out", - "/tools ..." - ] - }, - "outputs": { - "out": { - "from": "/out", - "packtype": "tar" - } - } - } + "type": "ipvm/effect", + "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + "do": "crud/read" } - ``` + -# 5 Exception Handling +# 6 Exception Handler Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. @@ -180,65 +359,9 @@ Note that effectful exception handlers that depend on specific capabilities (suc -# 6 Task Scheduler - - -# 7 Container +--------- -The outer wrapper of a job contains the -| Field | Type | Description | Required | -|-------------|--------------------|-------------------------------|----------| -| `type` | `"ipvm/job"` | Object type identifier | Yes | -| `version` | `"0.1.0"` | IPVM job version | Yes | -| `requestor` | DID | Requestor's DID | Yes | -| `run` | `{String => Task}` | Individual named tasks | Yes | -| `signature` | Varsig | Signature of all other fields | Yes | -``` json -{ - type: "ipvm/job" - version: "0.1.0" - reuqestor: "did:key:zAlice", - nonce: "xjd72gs_k", - run: { - start: { - type: "ipvm/effect", - with: "", - do: "ipvm/dnslink/resolve" - }, - left: { - type: "ipvm/wasm", - with: myWasm, - inputs: [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "database", "out": 0 } } - { "z": "QmFooBar" }, - ], - outputs: ["a", "b"] - }, - right: { - type: "ipvm/warpforge", - with: ____, - do: "build", - inputs: { - "rootfs": "abc", - "gawk": "def", - "data": "ghi" - }, - steps: { - awk-test: { - - } - } - }, - end: { - type: "ipvm/wasm", - - } - }, - signature: 10100010010011 -} -``` +NOTE TO SELF: on `crud/read`, we probably need some kind of max file size limit (and timeout obvs) diff --git a/job/foo.hs b/job/foo.hs deleted file mode 100644 index 0597b46..0000000 --- a/job/foo.hs +++ /dev/null @@ -1,40 +0,0 @@ -module IPVM.Formats where - --- Human spec -- - -data Career = Career - { version :: SemVer - , requestor :: DID - , nonce :: Word64 - , config :: Config -- FIXME - , label :: Text - , start :: NowOrLater - , globalConfig :: GlobalConfig - , verification :: VerificationConfig - , jobs :: Map Text Job - } - -data GlobalConfig = GlobalConfig - { wasmConfig :: WasmConfig - , effectConfig :: EffectConfig - , publishResults :: Bool - , auth :: [UCAN] -- FIXME move to post-negotaited? - } - -data Ref - = Absolute CID - | Relative { label :: Text, index :: Word8 } - -data Job = Job - { call :: Call - , jobConfig :: JobConfig - } - -data JobConfig - -data Call - = Effect { object :: Text, action :: Text, timeoutSecs :: Maybe Word64 } - | Wasm { wasm :: WasmBlob, input :: Map Text Ref, gas :: Word64, verification :: Verification } - - --- Runner spec -- diff --git a/job/job.json b/job/job.json index 6aa8ac2..925ca30 100644 --- a/job/job.json +++ b/job/job.json @@ -1,80 +1,67 @@ { - "type": "ipvm/job", - "version": "0.0.1", - "requestor": "did:key:zAlice", - "nonce": "ABCDEF", - "config": { - "label": "fission/run_the_reports", - "start": "asap", - "timeoutAt": 1667878395, - "maxGas": 4096, - "authz": ["QmUcan1", "QmUcan2"], // TODO move to post-negotiated? - "visibility": "public", - "verification": { - "method": "ipvm/optimistic-zk", - "min": 1, - "replication": 2, - "referee": "ipns://abcdefghi" - } + "type": "ipvm/job", + "version": "0.1.0", + "requestor": "did:key:zAlice", + "nonce": "xjd72gs_k", + "ipvm/effects": { + "read-dns": { + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" }, - "run": { - "wasm": "bafyWasm", - "args": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "with": "dnslink://example.com", "run": "dnslink/resolve" } } - { "z": "QmFooBar" }, - ], - "affinities": [], // TODO lives here, or in + "check-dns": { + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read", + "await": ["end"] }, - "before": { - "database": { - "with": "dnslink://example.com", - "run": "dnslink/resolve" - } + "write-dns": { + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/update", + "inputs": [ + { "value": { "from": "end" } }, + ], + "await": ["cas"] + } + }, + "ipvm/wasm": { + "left": { + "with": "QmLeftWasm", + "inputs": [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "from": "effects/read-dns" } } + { "z": "QmFooBar" }, + ], + "outputs": ["a", "b"] }, - "then": { - "effects": { - "bell": { - "run": "system/bell", - "on": "always" - } - }, - "jobs": { - "left": { - "run": { - "wasm": "bafyWasmLeft", - "args": ["bafySomeArg"] - }, - "then": { - "jobs": { - "subleft": { - "run": { - "wasm": "bafyWasmSubLeft", - "args": - } - } - } - } - }, - "right": { - "run": "b" - }, - "end": { - "run": { - "wasm": "bafyWasmEnd", - "input": { - "foo": "jobs/left/subleft/" - } - } - } + "right": { + "with": "bafyRightWasm", + "inputs": [ + { "foo": { "from": "effects/read-dns" } }, + { "bar": "bafy123" } + ], + "outputs": ["a", "b"] }, - "signature": "abcdef" -} - -{ - "type": "ipvm/deal", - "version": "0.0.1", - "accepter": "", - "job": "Qmabcdef" + "end": { + "with": "QmEndWasm", + "inputs": [ + { "a": { "from": "left" } }, + { "b": { "from": "right" } }, + { "c": 123 } + ] + }, + "cas": { + "with": "bafyCasWasm", + "inputs": [ + { "initial": "effects/read-dns" }, + { "latest": "effects/check-dns" } + ] + } + }, + "ipvm/exception": { + "format-message": { + "type": "ipvm/wasm", + "with": "bafyHandlerWasm" + } + }, + "signature": "abcdef" } diff --git a/job/job.png b/job/job.png deleted file mode 100644 index 80ecc60bc6cb67067a6d539b7b37f884b2a2b03c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116818 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�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 diff --git a/job/warpforge.json b/job/warpforge.json new file mode 100644 index 0000000..87f865a --- /dev/null +++ b/job/warpforge.json @@ -0,0 +1,36 @@ +{ + "type": "ipvm/job", + "version": "0.1.0", + "requestor": "did:key:zAlice", + "nonce": "xjd72gs_k", + "run": { + "container": { + "type": "ipvm/effect", + "with": "bafyWarpForge", + "do": "container/build", + "inputs": { + "rootfs": "abc", + "gawk": "def", + "data": "ghi" + } + }, + "awk-test": { + "type": "ipvm/warpforge", + "with": "container/out", + "do": "script/interpret", + "inputs": { + "interpreter": "bin/sh", + "contents": [ + "mkdir /out", + "/tools ..." + ] + }, + "outputs": { + "out": { + "from": "/out", + "packtype": "tar" + } + } + } + } +} From 933dfc4a46ebafbb215b81664e036a4496934983 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 21 Nov 2022 00:55:15 -0800 Subject: [PATCH 12/42] Saving WIP --- effect/README.md | 126 ++++++++++++++++ job/README.md | 378 +++++++++++++++++++---------------------------- job/example.josn | 83 +++++++++++ 3 files changed, 363 insertions(+), 224 deletions(-) create mode 100644 effect/README.md create mode 100644 job/example.josn diff --git a/effect/README.md b/effect/README.md new file mode 100644 index 0000000..3e2d6c4 --- /dev/null +++ b/effect/README.md @@ -0,0 +1,126 @@ + +## 5.1 Secrets + +Secrets MUST modelled as effects. Just as not every machine will have the ability to update a DNS record, not all will be able to decrypt data or sign data with a specific private key. These effects SHOULD default to private visibility. + +### 5.1.1 Signing + +| Field | Type | Description | Required | Default | +|----------|-----------------|---------------------------------|-------------|---------| +| `type` | `"ipvm/effect"` | Identify this job as a Wasm 1.0 | Yes | | +| `with` | DID | | Yes | | +| `do` | `"crypto/sign"` | | Yes | | +| `value` | String | | On mutation | | +| `public` | `Boolean` | RECOMMENDED not public | Yes | `false` | + + + +``` json +{ + "type": "ipvm/effect", + "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", + "do": "crypto/sign", + "inputs": [{ "value": "aBcDeF" }] +} +``` + +### 5.1.2 Out-of-Band Decryption + +``` json +{ + "type": "ipvm/effect", + "with": "ipns://alice.fission.name/supersecret", + "do": "crypto/decrypt", + "public": false, + "inputs": [{ "value": "aBcDeF" }] +} +``` + +### 5.1.3 In-Band Secrets + +Some cases require having direct access to a secret, such as a + +``` json +{ + "type": "ipvm/effect", + "with": "ipvm:secret:github.com/ipvm-wg/spec?secret=API_KEY_NAME", + "do": "secret/get", + "public": false, + "inputs": [{ "value": "aBcDeF" }] +} +``` + +## 5.2 DNS + +| Field | Type | Description | Required | +|---------|---------------------|-----------------------------------------------------------------------|-------------| +| `type` | `"ipvm/effect/dns"` | Identify this job as a Wasm 1.0 | Yes | +| `with` | URI | DNS URI (domain name or subdomain) | Yes | +| `do` | crud | Any ability in the `crud` namespace (e.g. `crud/read`, `crud/update`) | Yes | +| `value` | String | | On mutation | + +More specific uses MAY be built out of the primitive DNS resolver. + + + +Read from [DNSLink](https://dnslink.io) + +``` json +{ + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" +} +``` + +Update an A record + +``` json +{ + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=A", + "do": "crud/update", + "value": "12.345.67.890" +} +``` + +[did:dns](https://danubetech.github.io/did-method-dns/) + +``` json +{ + "type": "ipvm/effect", + "with": "dns://_key1._did.example.com?TYPE=URI", + "do": "crud/read" +} +``` + +## 5.3 IPNS + +[IPNS](https://docs.ipfs.tech/concepts/ipns/) + +``` json +{ + "type": "ipvm/effect", + "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + "do": "crud/read" +} +``` + +## 5.4 Randomness + +Randomness is RECOMMENDED to be souiderived from a trused high-entropy source, such as [drand](https://drand.love/). + +``` json +{ + "type": "ipvm/effect", + "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + "do": "crud/read" +} +``` + + + + + + +NOTE TO SELF: on `crud/read`, we probably need some kind of max file size limit (and timeout obvs) diff --git a/job/README.md b/job/README.md index 18dd143..3831a56 100644 --- a/job/README.md +++ b/job/README.md @@ -1,4 +1,4 @@ -# IPVM Job Spec v0.1.0 +# IPVM Job Specification v0.1.0 ## Editors @@ -7,7 +7,7 @@ ## Authors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) - +* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) _(TODO: Provisionally!)_ ## Language @@ -15,23 +15,25 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -The IPVM job configuration defines the global parameters for a proposed job, the dependenceis between steps, the distrinction between - -Any computing environment by defintion must include invocation. The IPVM Job configuration DSL provides configuration and dataflow for pure computation and effects. +An IPVM Job defines the global configuration for a proposed job, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Jobs is an envelope for both the configuration and content layers common to job specifications. # 1 Introduction -## 1.1 Motivation +The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Jobs reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Job spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. + +While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a job, they assume an established audience (which too ridig for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. -## 1.2 Design Philosophy +IPVM Jobs MUST be suitable for the proposal of jobs and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and ___. Jobs SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. + +## 1.1 Design Philosophy > 8. A programming language is low level when its programs require attention to the irrelevant. > > — Alan Perlis, Epigrams on Programming -While IPVM MAY execute arbitrary programs, IPVM Jobs are specified declaratively. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, and distribution. Such a specification also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. +While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Jobs are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. -On the other hand, IPVM Jobs are support few features and impose few constraints on what can be run. There is no first-class concept of persistent objects or loops. This layer of IPVM is only concerned with terminating execution. Loops, actors, vats, and so on MAY be implemented on top of IPVM Jobs by enqueuing new jobs using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). +These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Jobs by enqueuing new jobs using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). The core restrictions enforced by the design of IPVM Jobs are: @@ -47,321 +49,249 @@ While effects MUST be declared up front, they MAY also be emitted as output from > > — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design -While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The target is moving jobs and tasks between machines, logging, and debugging. As such IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. +While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving jobs and tasks between machines, logging, and execution. + +IPVM Jobs aim to provide a computational model with a clear contract ("few if any surprises") for the programmer. + +IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. ## 1.3 Security Considerations -Working with encrypted data and application secrets (section X.Y) is common practice for many jobs. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Job. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and never memoized. +> A program can create a controlled environment within which another, possible untrustworthy program, can be run safely [but] may leak, i.e., transmit [...] the input data which the customer gives it [...] We will call the problem of constraining a service [from leaking sensitive data] the confinement problem. +> +> Butler W. Lampson, A Note on the Confinement Problem, Communications of the ACM -# 2 Container +IPVM runs in trustless ("mutually suspicious") environments. Conceivably either a job proposer or service provider could be mallicious. To limit ___. -The outer wrapper of a job contains the +Working with encrypted data and application secrets (section X.Y) is common practice for many jobs. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Job. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and not distributed. -configuration vs content layers +While it is tempting to push authorization concerns to a serapate layer, this has historically lead systems to be built on fundamentally insecure primitives. As such, IPVM Jobs include security considerations directly. It is not possible to control the security model of external effects, but it is possible to secure the inbound boundary to IPVM. -| Field | Type | Description | Required | Default | -|-------------|--------------------|--------------------------------|----------|---------| -| `type` | `"ipvm/job"` | Object type identifier | Yes | | -| `version` | `"0.1.0"` | IPVM job version | Yes | | -| `requestor` | DID | Requestor's DID | Yes | | -| `nonce` | String | | Yes | | -| `parent` | `CID | null` | | No | `null` | -| `meta` | Object | | No | `{}` | -| `config` | `IpvmConfig` | | No | | -| `run` | `{String => Task}` | Named tasks | Yes | | -| `signature` | Varsig | Signature of serialized fields | Yes | | +Pure computation is always allowed as long as it terminates in a fixed number of steps. An executor -## 2.1 `type` Field +Shared-nothing architecture. Even if shared memory is used, it MUST be controlled externally via the effect system (i.e. an outside agent). -The `type` field MUST be set to `ipvm/job`. +# 2 Envelope -...FIXME more text... +The outer wrapper of a job contains the information -## 2.2 `version` +FIXME add IPLD schema -## 2.X Examples +| Field | Type | Description | Required | Default | +|-------------|-----------------------------|-----------------------------------------|----------|---------| +| `type` | `"ipvm/job"` | Object type identifier | Yes | | +| `version` | `"0.1.0"` | IPVM job version | Yes | | +| `requestor` | DID | Requestor's DID | Yes | | +| `nonce` | String | Unique nonce | Yes | | +| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | +| `meta` | Object | | No | `{}` | +| `config` | `IpvmConfig` | | No | | +| `run` | `{String => Task}` | Named tasks | Yes | | +| `exception` | `Task.Wasm` | | | | +| `signature` | Varsig | Signature of serialized fields | Yes | | -### 2.X.1 Pure +## 2.1 Fields -Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). +## 2.1.1 Type -``` json -{ - "type": "ipvm/job", - "version": "0.1.0", - "requestor": "did:key:zAlice", - "nonce": "o3--8Gdu5", - "run": { - "left": { - "type": "ipvm/wasm", // or make this a label for the microkernel? - "kernel: "Qm12345", // Or here? - "with": leftWasm, - "inputs": [], - "outputs": ["a", "b"] - }, - "right": { - "type": "ipvm/wasm", - "with": "rightWasm", - "inputs": [ - { "bar": "bafy123" } - ], - "outputs": ["a", "b"] - }, - "end": { - "type": "ipvm/wasm", - "with": "QmEndWasm", - "inputs": [ - { "a": { "from": "left" } }, - { "b": { "from": "right" } }, - { "c": 123 } - ] - } - } - "signature": "abcdef" -} -``` +The `type` field MUST be set to `ipvm/job`. This field together with the `version` field indicates the expected fields and minimal semantics for the job. + +## 2.1.2 Version + +The `version` field MUST be set to `0.1.0`. This field together with the `type` field indicates the expected fields and minimal semantics for the job. + +## 2.1.3 Requestor + +The `requestor` field MUST be set to the DID of the agent requesting the job. The Rquestor is the only identified agent in a Job. + +The `signature` field MUST validate with a public key associated with the Requestor's DID. + +## 2.1.4 Nonce + +The `nonce` field MUST be a one-time-use random string. At least 12 random bytes encoded as base64 is RECOMMENDED. + +## 2.1.5 Parent + +The OPTIONAL `parent` field contains the CID of the IPVM Task that initiated it (if any). + +## 2.1.6 Meta + +The `meta` field contains a user-definable JSON object. This is useful for including things like tags, comments, and so on. -## 2.X.2 Effectful +## 2.1.7 Global Configuration -Here is an example of a nontrivial IPVM job which reads from DNS, performs several jobs on the value, and atomically performs a DNS update with the output value. +The ___ FIXME + +## 2.1.8 Run + +The `run` field contains all of the IPVM Tasks set to run in this Job, each labelled by a human-readable key. + +See the [Task](FIXME) section for more. + +## 2.1.9 Exception + +The `exception` field contains a Task with predefined inputs. See the [Exception Handling](FIXME) section for more. + +## 2.1.10 Signature + +The signature of the CID represented by the other fields. + +## 2.2 Example + +Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). ``` json { "type": "ipvm/job", "version": "0.1.0", "requestor": "did:key:zAlice", - "nonce": "xjd72gs_k", - "run": { - "read-dns": { - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" - }, - "check-dns": { - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" - "inputs": [ - { "_": { "from": "end" } } - ] - }, - "write-dns": { - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/update", - "inputs": [ - { "value": { "from": "end" } } - { "_": { "from": "cas" } } - ], - - } + "nonce": "o3--8Gdu5", + "tasks": { "left": { - "type": "ipvm/wasm", // or make this a label for the microkernel? - "kernel: "Qm12345", // Or here? - "with": leftWasm, - "inputs": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "read-dns" } } - { "z": "QmFooBar" }, - ], + "type": "ipvm/wasm", + "wasm/0.1.0": "bafyLeftWasm", + "inputs": [], "outputs": ["a", "b"] }, "right": { "type": "ipvm/wasm", - "with": "rightWasm", + "wasm/0.1.0": "QmRightWasm", "inputs": [ - { "foo": { "from": "read-dns/out" } }, { "bar": "bafy123" } ], "outputs": ["a", "b"] }, "end": { - "type": "ipvm/wasm", - "with": "QmEndWasm", + "wasm": "QmEndWasm", "inputs": [ { "a": { "from": "left" } }, { "b": { "from": "right" } }, { "c": 123 } ] - }, - "cas": { - "type": "ipvm/wasm", - "with": "cafyCasWasm", - "inputs": [ - { "initial": "read-dns" }, - { "latest": "check-dns" } - ] } }, - "exception": { - "format-message": { - type: "ipvm/wasm", - with: handlerWasm - } - }, "signature": "abcdef" } ``` -# 3 Task +# 3 Tasks -While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. The ordering MUST be implied from the inputs, flowing source to sink. +While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. | Field | Type | Description | Required | Default | |----------|---------------------|----------------------------------|----------|---------| | `type` | `string` | The type of task (Wasm, etc) | Yes | | -| `with` | `CID` | Reference to the Wasm to run | Yes | | +| `with` | `CID or URI` | Reference to the Wasm to run | Yes | | | `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | | `auth` | `UCAN[]` | | Yes | | | `secret` | `Boolean` | | No | `True` | | `meta` | `Object` | | No | `{}` | -## 3.1 Input - -# 4 Pure Wasm +## 3.1 Fields -When treated as a black box, the deterministic subset of Wasm may be treated as a pure function. - -The Wasm configuration MUST extend the core task type with the following fields: +### 3.1.1 `type` -| Field | Type | Description | Required | Default | -|----------|---------------------|----------------------------------|----------|-------------------------------| -| `type` | `"ipvm/wasm/1.0"` | Identify this task as Wasm 1.0 | Yes | | -| `with` | CID | Reference to the Wasm to run | Yes | | -| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `maxGas` | Integer | | No | 1000 | +The `type` field is used to declare the shape of the objet. This field MUST be either `ipvm/wasm` for pure Wasm, or `ipvm/effect` for effectful computation. -# 5 Effects +### 3.1.2 `with` Resource -The `with` field MAY be filled from a relative value (previous step) +The `with` field MUST contain a CID or URI of the resource to interact with. For example, this MAY be the Wasm to execute, or the URL of a web server to send a message to. -## 5.1 Secrets +### 3.1.3 `input` -Secrets MUST modelled as effects. Just as not every machine will have the ability to update a DNS record, not all will be able to decrypt data or sign data with a specific private key. These effects SHOULD default to private visibility. +FIXME define mapping to ABI / WIT -### 5.1.1 Signing +The `input` field contains an array of objects. Each entry represents an association of human-readable labels to values (or references to values). The index is significant, since many tasks take only positonal input. -| Field | Type | Description | Required | Default | -|----------|-----------------|---------------------------------|-------------|---------| -| `type` | `"ipvm/effect"` | Identify this job as a Wasm 1.0 | Yes | | -| `with` | DID | | Yes | | -| `do` | `"crypto/sign"` | | Yes | | -| `value` | String | | On mutation | | -| `public` | `Boolean` | RECOMMENDED not public | Yes | `false` | +Values MUST be serialized as ______. If an input is given as an object, it MUST be treated as - +For ex -``` json -{ - "type": "ipvm/effect", - "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", - "do": "crypto/sign", - "inputs": [{ "value": "aBcDeF" }] -} -``` +# 4 Pure Wasm -### 5.1.2 Out-of-Band Decryption +Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly availabel via WASI or similar. -``` json -{ - "type": "ipvm/effect", - "with": "ipns://alice.fission.name/supersecret", - "do": "crypto/decrypt", - "public": false, - "inputs": [{ "value": "aBcDeF" }] -} -``` +Since + +The Wasm configuration MUST extend the core task type with the following fields: -### 5.1.3 In-Band Secrets +| Field | Type | Description | Required | Default | +|-----------|---------------------|----------------------------------|----------|-------------------------------| +| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | +| `version` | `1.0` | The Wasm module's Wasm version | Yes | | +| `with` | CID | Reference to the Wasm to run | Yes | | +| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `maxgas` | Integer | | No | 1000 | -Some cases require having direct access to a secret, such as a +## 4.1 `type` -``` json -{ - "type": "ipvm/effect", - "with": "ipvm:secret:github.com/ipvm-wg/spec?secret=API_KEY_NAME", - "do": "secret/get", - "public": false, - "inputs": [{ "value": "aBcDeF" }] -} -``` +The `type` field declares this object to be an IPVM Wasm configuration. The value MUST be `ipvm/wasm`. -## 5.2 DNS +## 4.2 `with` -| Field | Type | Description | Required | -|---------|---------------------|-----------------------------------------------------------------------|-------------| -| `type` | `"ipvm/effect/dns"` | Identify this job as a Wasm 1.0 | Yes | -| `with` | URI | DNS URI (domain name or subdomain) | Yes | -| `do` | crud | Any ability in the `crud` namespace (e.g. `crud/read`, `crud/update`) | Yes | -| `value` | String | | On mutation | +The `with` field declares the Wasm module to load via CID. -More specific uses MAY be built out of the primitive DNS resolver. +Note that the - +## 4.3 `input` -Read from [DNSLink](https://dnslink.io) +## 4.4 `maxgas` -``` json -{ - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" -} -``` +The `maxgas` field specifies the upper limit in gas that this task may consume. -Update an A record +For the gas schedule, please see the [gas schedule spec]. -``` json -{ - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=A", - "do": "crud/update", - "value": "12.345.67.890" -} -``` - -[did:dns](https://danubetech.github.io/did-method-dns/) +# 5 Effects -``` json -{ - "type": "ipvm/effect", - "with": "dns://_key1._did.example.com?TYPE=URI", - "do": "crud/read" -} -``` +The contract for effects is different from pure computation. As effects by definition interact with the "real world", -## 5.3 IPNS +The `with` field MAY be filled from a relative value (previous step) -[IPNS](https://docs.ipfs.tech/concepts/ipns/) +| Field | Type | Description | Required | Default | +|-----------|-----------------|--------------------------------|-------------|---------| +| `type` | `"ipvm/effect"` | Identify this job as an effect | Yes | | +| `with` | URI | | Yes | | +| `do` | `"crypto/sign"` | | Yes | | +| `value` | String | | On mutation | | +| `timeout` | Integer | Timeout in milliseconds | No | 1000 | ``` json { - "type": "ipvm/effect", - "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", - "do": "crud/read" + "type": "ipvm/effect", + "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", + "do": "crypto/sign", + "inputs": [ + { "value": { "from": "earlierStep" } } + ] } ``` - +## 5.1 `type` # 6 Exception Handler Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. -It is often desirable to fire a specific job in the case that a job fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. +It is often desirable to fire a specific job in the case that a job fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). Each task MAY include a failure job to run on failure. -Note that effectful exception handlers that depend on specific capabilities (such as network access) MAY fail for the same reason as the job that caused the exception to be thrown. Running a pure effect is RECOMMENDED. - +Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the job that caused the exception to be thrown. Running a pure value is RECOMMENDED. +# 7 Related Work and Prior Art +AWS Lambda job specs ---------- +E Language, CapNet +It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), +# 8 Acknowledgments -NOTE TO SELF: on `crud/read`, we probably need some kind of max file size limit (and timeout obvs) +* Steb +* Mel +* Christine +* Blaine Cook +* Luke Marsden diff --git a/job/example.josn b/job/example.josn new file mode 100644 index 0000000..4b557f8 --- /dev/null +++ b/job/example.josn @@ -0,0 +1,83 @@ + +## 2.3.2 Effectful + +Here is an example of a nontrivial IPVM job which reads from DNS, performs several jobs on the value, and atomically performs a DNS update with the output value. + +``` json +{ + "type": "ipvm/job", + "version": "0.1.0", + "requestor": "did:key:zAlice", + "nonce": "xjd72gs_k", + "run": { + "read-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" + }, + "check-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/read" + "inputs": [ + { "_": { "from": "end" } } + ] + }, + "write-dns": { + "type": "ipvm/effect", + "with": "dns://_dnslink.example.com?TYPE=TXT", + "do": "crud/update", + "inputs": [ + { "value": { "from": "end" } } + { "_": { "from": "cas" } } + ], + + } + "left": { + "type": "ipvm/wasm", // or make this a label for the microkernel? + "kernel: "Qm12345", // Or here? + "with": leftWasm, + "inputs": [ + { "w": "Qm123456" }, + { "x": "Qmabcdef" }, + { "y": { "from": "read-dns" } } + { "z": "QmFooBar" }, + ], + "outputs": ["a", "b"] + }, + "right": { + "type": "ipvm/wasm", + "with": "rightWasm", + "inputs": [ + { "foo": { "from": "read-dns/out" } }, + { "bar": "bafy123" } + ], + "outputs": ["a", "b"] + }, + "end": { + "type": "ipvm/wasm", + "with": "QmEndWasm", + "inputs": [ + { "a": { "from": "left" } }, + { "b": { "from": "right" } }, + { "c": 123 } + ] + }, + "cas": { + "type": "ipvm/wasm", + "with": "cafyCasWasm", + "inputs": [ + { "initial": "read-dns" }, + { "latest": "check-dns" } + ] + } + }, + "exception": { + "format-message": { + type: "ipvm/wasm", + with: handlerWasm + } + }, + "signature": "abcdef" +} +``` From 024e9521a6fd34f88dcf374b98e90f1ca6f58fd8 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 22 Nov 2022 00:11:43 -0800 Subject: [PATCH 13/42] Flesh out pipeling, rename several fields (e.g. run) --- job/README.md | 232 ++++++++++++++++++++++++++++++++--------------- job/example.josn | 4 +- 2 files changed, 163 insertions(+), 73 deletions(-) diff --git a/job/README.md b/job/README.md index 3831a56..5fa82dd 100644 --- a/job/README.md +++ b/job/README.md @@ -15,7 +15,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -An IPVM Job defines the global configuration for a proposed job, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Jobs is an envelope for both the configuration and content layers common to job specifications. +An IPVM Job defines the global configuration for a proposed job, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Jobs is an envelope for both the configuration and content layers common to declarative job specifications. # 1 Introduction @@ -49,11 +49,7 @@ While effects MUST be declared up front, they MAY also be emitted as output from > > — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design -While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving jobs and tasks between machines, logging, and execution. - -IPVM Jobs aim to provide a computational model with a clear contract ("few if any surprises") for the programmer. - -IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. +While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving jobs and tasks between machines, logging, and execution. IPVM Jobs aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. ## 1.3 Security Considerations @@ -73,22 +69,25 @@ Shared-nothing architecture. Even if shared memory is used, it MUST be controlle # 2 Envelope -The outer wrapper of a job contains the information - -FIXME add IPLD schema - -| Field | Type | Description | Required | Default | -|-------------|-----------------------------|-----------------------------------------|----------|---------| -| `type` | `"ipvm/job"` | Object type identifier | Yes | | -| `version` | `"0.1.0"` | IPVM job version | Yes | | -| `requestor` | DID | Requestor's DID | Yes | | -| `nonce` | String | Unique nonce | Yes | | -| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | -| `meta` | Object | | No | `{}` | -| `config` | `IpvmConfig` | | No | | -| `run` | `{String => Task}` | Named tasks | Yes | | -| `exception` | `Task.Wasm` | | | | -| `signature` | Varsig | Signature of serialized fields | Yes | | +The outer wrapper of a job MUST contain the following fields: + +| Field | Type | Description | Required | Default | +|----------------|-----------------------------|-------------------------------------------|----------|---------| +| `type` | `"ipvm/job"` | Object type identifier | Yes | | +| `version` | `"0.1.0"` | IPVM job version | Yes | | +| `requestor` | DID | Requestor's DID | Yes | | +| `nonce` | String | Unique nonce | Yes | | +| `verification` | `{"optimistic": 2} or null` | | Yes | | +| `meta` | `&Object` | User-defined object (tags, comments, etc) | No | `{}` | +| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | +| `defaults` | `IpvmConfig` | | No | `{}` | +| `tasks` | `{String => Task}` | Named tasks | Yes | | +| `exception` | `Task.Wasm` | | No | `null` | +| `signature` | Varsig | Signature of serialized fields | Yes | | + +``` ipldsch +FIXME +``` ## 2.1 Fields @@ -118,10 +117,12 @@ The OPTIONAL `parent` field contains the CID of the IPVM Task that initiated it The `meta` field contains a user-definable JSON object. This is useful for including things like tags, comments, and so on. -## 2.1.7 Global Configuration + -The ___ FIXME +## 2.1.7 Defaults +The global `defaults` object (FIXME section X.Y) sets the configuration for the job itself, and defaults for tasks. + ## 2.1.8 Run The `run` field contains all of the IPVM Tasks set to run in this Job, each labelled by a human-readable key. @@ -138,7 +139,24 @@ The signature of the CID represented by the other fields. ## 2.2 Example -Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). +Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). Pictorally: + +``` +┌─────────┐ ┌─────────┐ +│ │ │ │ +│ left │ │ right │ +│ │ │ │ +└────────┬┘ └─┬───────┘ + │ │ + │ │ + ┌─▼──────▼┐ + │ │ + │ end │ + │ │ + └─────────┘ +``` + +This is fully configured as: ``` json { @@ -146,25 +164,27 @@ Here is a nontrivial example of two tasks (`left` and `right`) used as input to "version": "0.1.0", "requestor": "did:key:zAlice", "nonce": "o3--8Gdu5", + "verification": {"optimistic": 2}, "tasks": { "left": { "type": "ipvm/wasm", - "wasm/0.1.0": "bafyLeftWasm", + "version": "0.1.0", + "wasm": "bafkreiecadaahndb55cgvemhctwoojcc4hv4alogybpqndzj4mq7brixcy", "inputs": [], - "outputs": ["a", "b"] + "outputs": ["foo", "bar"] }, "right": { "type": "ipvm/wasm", - "wasm/0.1.0": "QmRightWasm", + "wasm": "bafkreidrvex7kbqiow7gbqzvj452hr3vbifmfvyd55qicfwrw6xvq3qnlq", "inputs": [ - { "bar": "bafy123" } - ], - "outputs": ["a", "b"] + { "quux": "bafy123" } + ] }, "end": { - "wasm": "QmEndWasm", + "type": "ipvm/wasm", + "wasm": "bafkreihvr3nup2lpny3ip3hkqv7s7ggq5wit5dkvyaexztnl54rkrlbdhe", "inputs": [ - { "a": { "from": "left" } }, + { "a": { "from": "left", "output": "bar" } }, { "b": { "from": "right" } }, { "c": 123 } ] @@ -174,32 +194,45 @@ Here is a nontrivial example of two tasks (`left` and `right`) used as input to } ``` -# 3 Tasks +# 3 Global Defaults + +The global defaults object contains options for the Job itself, as well as cascading defaults for Tasks. + +| Field | Type | Description | Required | Default | +|-----------|--------------|------------------------------|----------|---------| +| | | | | | +| `wasm` | `CID or URI` | Reference to the Wasm to run | Yes | | +| `effects` | | | | | + +## 3.1 Global Wasm Configuration + +## 3.2 Global Effects Configuration + +# 4 Tasks While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. -| Field | Type | Description | Required | Default | -|----------|---------------------|----------------------------------|----------|---------| -| `type` | `string` | The type of task (Wasm, etc) | Yes | | -| `with` | `CID or URI` | Reference to the Wasm to run | Yes | | -| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `auth` | `UCAN[]` | | Yes | | -| `secret` | `Boolean` | | No | `True` | -| `meta` | `Object` | | No | `{}` | +| Field | Type | Description | Required | Default | +|-----------|-----------|------------------------------|----------|-----------| +| `type` | `string` | The type of task (Wasm, etc) | Yes | | +| `version` | SemVer | | No | `"0.1.0"` | +| `auth` | `&UCAN[]` | | No | `[]` | +| `secret` | `Boolean` | | No | `False` | +| `meta` | `Object` | | No | `{}` | -## 3.1 Fields +## 4.1 Fields -### 3.1.1 `type` +### 4.1.1 `type` The `type` field is used to declare the shape of the objet. This field MUST be either `ipvm/wasm` for pure Wasm, or `ipvm/effect` for effectful computation. -### 3.1.2 `with` Resource +### 4.1.2 `with` Resource The `with` field MUST contain a CID or URI of the resource to interact with. For example, this MAY be the Wasm to execute, or the URL of a web server to send a message to. -### 3.1.3 `input` +### 4.1.3 `args` FIXME define mapping to ABI / WIT @@ -209,7 +242,51 @@ Values MUST be serialized as ______. If an input is given as an object, it MUST For ex -# 4 Pure Wasm +## 4.2 Pipelining + +The output of one task is often the input to another. This is called pipelining, and MUST form one or more directed acyclic graphs (DAGs). Graphs MAY be unrooted. + +For example, this is a legal set of task graphs in a single job. + +``` +┌──────────────────Job Tasks──────────────────┐ +│ │ +│ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ │ │ │ +│ └───────┬─┘ └─┬───────┘ └────┬────┘ │ +│ │ │ │ │ +│ │ │ │ │ +│ ┌─▼──────▼─┐ ┌────▼────┐ │ +│ │ │ │ │ │ +│ │ │ │ │ │ +│ │ │ │ │ │ +│ └─┬──────┬─┘ └─────────┘ │ +│ │ │ │ +│ │ │ │ +│ ┌───────▼─┐ ┌─▼───────┐ │ +│ │ │ │ │ │ +│ │ │ │ │ │ +│ │ │ │ │ │ +│ └─────────┘ └─────────┘ │ +│ │ +│ │ +└─────────────────────────────────────────────┘ +``` + +Pipelining is acheived via dataflow. Every task is given a name, and its output(s) MUST be referenced by either the task's CID or its name inside the task map. If the task has more than one output, the output MUST be referenced by index (starting from 0) or name. + +``` +{"from": "previousTask"} +{"from": "previousTask", "out": 3} +{"from": "bafkreifovuswf6ss7czm5gk6ibnd7klhoojhynmiydj6cf7p2yjdsevlga", "out": "firstName" } +``` + +References by CID MAY be tasks that executed outside of the current job. If the task was not yet executed, it MUST NOT run inside this Job. + +# 5 Pure Wasm Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly availabel via WASI or similar. @@ -220,22 +297,23 @@ The Wasm configuration MUST extend the core task type with the following fields: | Field | Type | Description | Required | Default | |-----------|---------------------|----------------------------------|----------|-------------------------------| | `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | -| `version` | `1.0` | The Wasm module's Wasm version | Yes | | -| `with` | CID | Reference to the Wasm to run | Yes | | -| `input` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `maxgas` | Integer | | No | 1000 | +| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | +| `run` | CID | Reference to the Wasm to run | Yes | | +| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `secret` | Boolean | | No | `False` | +| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | -## 4.1 `type` +## 5.1 `type` The `type` field declares this object to be an IPVM Wasm configuration. The value MUST be `ipvm/wasm`. -## 4.2 `with` +## 5.2 `with` The `with` field declares the Wasm module to load via CID. Note that the -## 4.3 `input` +## 5.3 `input` ## 4.4 `maxgas` @@ -243,34 +321,39 @@ The `maxgas` field specifies the upper limit in gas that this task may consume. For the gas schedule, please see the [gas schedule spec]. -# 5 Effects +# 6 Effects -The contract for effects is different from pure computation. As effects by definition interact with the "real world", +The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. The `with` field MAY be filled from a relative value (previous step) -| Field | Type | Description | Required | Default | -|-----------|-----------------|--------------------------------|-------------|---------| -| `type` | `"ipvm/effect"` | Identify this job as an effect | Yes | | -| `with` | URI | | Yes | | -| `do` | `"crypto/sign"` | | Yes | | -| `value` | String | | On mutation | | -| `timeout` | Integer | Timeout in milliseconds | No | 1000 | +| Field | Type | Description | Required | Default | +|---------------|-----------------|---------------------------------------|----------|---------| +| `type` | `"ipvm/effect"` | Identify this job as an effect | Yes | | +| `version` | SemVer | IPVM effect schema version | No | `0.1.0` | +| `to` | URI | | Yes | | +| `do` | ability | | Yes | | +| `args` | `[{}]` | | No | `[]` | +| `timeout` | Integer | Timeout in milliseconds | No | `5000` | +| `auth` | `[&UCAN]` | | No | `[]` | + + + ``` json { "type": "ipvm/effect", - "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", + "to": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", "do": "crypto/sign", - "inputs": [ + "args": [ { "value": { "from": "earlierStep" } } ] } ``` -## 5.1 `type` +## 6.1 `type` -# 6 Exception Handler +# 7 Exception Handler Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. @@ -280,15 +363,22 @@ Each task MAY include a failure job to run on failure. Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the job that caused the exception to be thrown. Running a pure value is RECOMMENDED. -# 7 Related Work and Prior Art +# 8 Related Work and Prior Art AWS Lambda job specs - -E Language, CapNet +OCI +E Language +CapNet +Project Naiad +Bloom +PACT/HydroLogic +BucketVM +Bacalhau +AquaVM It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), -# 8 Acknowledgments +# 9 Acknowledgments * Steb * Mel diff --git a/job/example.josn b/job/example.josn index 4b557f8..61b9eff 100644 --- a/job/example.josn +++ b/job/example.josn @@ -25,9 +25,9 @@ Here is an example of a nontrivial IPVM job which reads from DNS, performs sever }, "write-dns": { "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", "do": "crud/update", - "inputs": [ + "to": "dns://_dnslink.example.com?TYPE=TXT", + "args": [ { "value": { "from": "end" } } { "_": { "from": "cas" } } ], From ab7961ad024630a9f47d576e376a99afeff1dfbb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 22 Nov 2022 00:33:14 -0800 Subject: [PATCH 14/42] Start to IPLDify --- job/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/job/README.md b/job/README.md index 5fa82dd..05a779c 100644 --- a/job/README.md +++ b/job/README.md @@ -87,6 +87,28 @@ The outer wrapper of a job MUST contain the following fields: ``` ipldsch FIXME + +type Job struct { + ver String + req DID + nnc String + vfy Verification + meta &{String:String} implicit {} + par &Task optional nullable + dfl Config implicit {} + tsks {String: Task} + exc &Wasm optional nullable + sig +} + +type Verification union { + | OptimisticVerification + | "snark" +} representation keyed + +type OptimisticVerification struct { + optimistic Integer +} ``` ## 2.1 Fields From ab957526c39e6235db0f7f16e5455f164fd9a164 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 23 Nov 2022 22:03:30 -0800 Subject: [PATCH 15/42] job -> workflow, break out task spec --- task/README.md | 194 ++++++++++++++++++++++++++++++++++++ {job => workflow}/README.md | 188 +++++++++------------------------- 2 files changed, 241 insertions(+), 141 deletions(-) create mode 100644 task/README.md rename {job => workflow}/README.md (50%) diff --git a/task/README.md b/task/README.md new file mode 100644 index 0000000..560cdde --- /dev/null +++ b/task/README.md @@ -0,0 +1,194 @@ +# IPVM Task Specification v0.1.0 + +## Editors + +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) + +## Authors + +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) +* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) _(TODO: Provisionally!)_ + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). + +# 0 Abstract + +Tasks are the smallest unit of work in an IPVM workflow. Each Task is restricted to a single type, such as Wasm or effects like an HTTP `GET` request. + +# 1 Introduction + +Tasks describe everything required to execute the job. While all Tasks share some things in common, the details MAY be quite different. + +Tasks can be broken into categories: + +1. Pure +2. Effectful + * Destructive + * Nondestructive + +Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from turning energy into heat). + +Effects interact with the world in some way . Reading from a database, sending an email, and firing missiles are all kinds of effect. + +One way to represent the difference between these pictorally is with box-and-wire diagrams. Here computation is drawn as a box. Explicit input and output are drawn as horizontal arrows, with input on the left and output on the right. Effects are drawn as vertical-pointing arrows. + +``` + Safe + │ ┌─ ─┐ + │ │ ┌─────────────┐ │ + │ │ │ │ │ + │ │ │ │ │ + │ Pure │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ └─ │ + │ │ + │ │ + │ │ Nondestructive + │ │ + │ │ + │ ┌─ │ + │ │ │ ┌─────────────┐ │ + │ │ └───► │ │ + │ │ │ │ │ + │ │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ │ ─┘ + │ │ + │ │ + │ Effectful │ + │ │ + │ │ + │ │ ▲ ─┐ + │ │ ┌─────────────┐ │ │ + │ │ │ ├──┘ │ + │ │ │ │ │ + │ │ ──────► ├────► │ Destructive + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + ▼ └─ ─┘ +Unsafe +``` + +## 1.1 Pure Functions + +Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. + +## 1.2 Nondestructive Effects + +For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. + +Nondestructive effects can be retried or raced safely. Each nondestructive invocation is unique, and need to be attested from a trusted source. Once their value enters the IPVM system, it is treated as a pure value. + +## 1.3 Destructive Effects + +Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. + +# 2 Envelope + + +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. + +| Field | Type | Description | Required | Default | +|-----------|-----------|------------------------------|----------|-----------| +| `type` | `string` | The type of task (Wasm, etc) | Yes | | +| `version` | SemVer | | No | `"0.1.0"` | +| `auth` | `&UCAN[]` | | No | `[]` | +| `secret` | `Boolean` | | No | `False` | +| `meta` | `Object` | | No | `{}` | + +## 2.1 Fields + +### 2.1.1 `type` + +The `type` field is used to declare the shape of the objet. This field MUST be either `ipvm/wasm` for pure Wasm, or `ipvm/effect` for effectful computation. + +### 2.1.2 `with` Resource + +The `with` field MUST contain a CID or URI of the resource to interact with. For example, this MAY be the Wasm to execute, or the URL of a web server to send a message to. + +### 2.1.3 `args` + +FIXME define mapping to ABI / WIT + +The `input` field contains an array of objects. Each entry represents an association of human-readable labels to values (or references to values). The index is significant, since many tasks take only positonal input. + +Values MUST be serialized as ______. If an input is given as an object, it MUST be treated as + +For ex + + + + +# 3 Pure Wasm + +Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly availabel via WASI or similar. + +Since + +The Wasm configuration MUST extend the core task type with the following fields: + +| Field | Type | Description | Required | Default | +|-----------|---------------------|----------------------------------|----------|-------------------------------| +| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | +| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | +| `run` | CID | Reference to the Wasm to run | Yes | | +| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `secret` | Boolean | | No | `False` | +| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | + +## 3.1 `type` + +The `type` field declares this object to be an IPVM Wasm configuration. The value MUST be `ipvm/wasm`. + +## 3.2 `with` + +The `with` field declares the Wasm module to load via CID. + +Note that the + +## 3.3 `input` + +## 3.4 `maxgas` + +The `maxgas` field specifies the upper limit in gas that this task may consume. + +For the gas schedule, please see the [gas schedule spec]. + +# 4 Effects + +The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. + +The `with` field MAY be filled from a relative value (previous step) + +| Field | Type | Description | Required | Default | +|-----------|-----------------|-------------------------------------|----------|---------| +| `type` | `"ipvm/effect"` | Identify this workflow as an effect | Yes | | +| `version` | SemVer | IPVM effect schema version | No | `0.1.0` | +| `using` | URI | | Yes | | +| `do` | ability | | Yes | | +| `args` | `[{}]` | | No | `[]` | +| `timeout` | Integer | Timeout in milliseconds | No | `5000` | +| `auth` | `[&UCAN]` | | No | `[]` | + + + + +``` json +{ + "type": "ipvm/effect", + "to": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", + "do": "crypto/sign", + "args": [ + { "value": { "from": "earlierStep" } } + ] +} +``` + +## 4.1 `type` diff --git a/job/README.md b/workflow/README.md similarity index 50% rename from job/README.md rename to workflow/README.md index 05a779c..ac9903e 100644 --- a/job/README.md +++ b/workflow/README.md @@ -1,4 +1,4 @@ -# IPVM Job Specification v0.1.0 +# IPVM Workflow Specification v0.1.0 ## Editors @@ -15,15 +15,15 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -An IPVM Job defines the global configuration for a proposed job, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Jobs is an envelope for both the configuration and content layers common to declarative job specifications. +An IPVM Workflow defines the global configuration for a proposed workflow, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Workflows is an envelope for both the configuration and content layers common to declarative workflow specifications. # 1 Introduction -The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Jobs reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Job spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. +The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. -While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a job, they assume an established audience (which too ridig for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. +While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a workflow, they assume an established audience (which too ridig for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. -IPVM Jobs MUST be suitable for the proposal of jobs and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and ___. Jobs SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. +IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and ___. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. ## 1.1 Design Philosophy @@ -31,14 +31,14 @@ IPVM Jobs MUST be suitable for the proposal of jobs and negotiation with provude > > — Alan Perlis, Epigrams on Programming -While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Jobs are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. +While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Workflows are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. -These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Jobs by enqueuing new jobs using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). +These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). -The core restrictions enforced by the design of IPVM Jobs are: +The core restrictions enforced by the design of IPVM Workflows are: 1. Execution MUST terminate in finite time -2. Job tasks MUST form a partial order +2. Workflow tasks MUST form a partial order 3. Effects MUST be managed by the runtime and declared ahead of time While effects MUST be declared up front, they MAY also be emitted as output from pure computation (see the core spec for more). This provides a "legal" escape hatch for building higher-level abstraction that incorporate effects. @@ -49,7 +49,7 @@ While effects MUST be declared up front, they MAY also be emitted as output from > > — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design -While higher-level interfaces over IPVM Jobs MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving jobs and tasks between machines, logging, and execution. IPVM Jobs aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM jobs follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. +While higher-level interfaces over IPVM Workflows MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving workflows and tasks between machines, logging, and execution. IPVM Workflows aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM workflows follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. ## 1.3 Security Considerations @@ -57,11 +57,11 @@ While higher-level interfaces over IPVM Jobs MAY be used, ultimately configurati > > Butler W. Lampson, A Note on the Confinement Problem, Communications of the ACM -IPVM runs in trustless ("mutually suspicious") environments. Conceivably either a job proposer or service provider could be mallicious. To limit ___. +IPVM runs in trustless ("mutually suspicious") environments. Conceivably either a workflow proposer or service provider could be mallicious. To limit ___. -Working with encrypted data and application secrets (section X.Y) is common practice for many jobs. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Job. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and not distributed. +Working with encrypted data and application secrets (section X.Y) is common practice for many workflows. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Workflow. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and not distributed. -While it is tempting to push authorization concerns to a serapate layer, this has historically lead systems to be built on fundamentally insecure primitives. As such, IPVM Jobs include security considerations directly. It is not possible to control the security model of external effects, but it is possible to secure the inbound boundary to IPVM. +While it is tempting to push authorization concerns to a serapate layer, this has historically lead systems to be built on fundamentally insecure primitives. As such, IPVM Workflows include security considerations directly. It is not possible to control the security model of external effects, but it is possible to secure the inbound boundary to IPVM. Pure computation is always allowed as long as it terminates in a fixed number of steps. An executor @@ -69,26 +69,26 @@ Shared-nothing architecture. Even if shared memory is used, it MUST be controlle # 2 Envelope -The outer wrapper of a job MUST contain the following fields: +The outer wrapper of a workflow MUST contain the following fields: -| Field | Type | Description | Required | Default | +| Field | Type | Description | Required | Default | |----------------|-----------------------------|-------------------------------------------|----------|---------| -| `type` | `"ipvm/job"` | Object type identifier | Yes | | -| `version` | `"0.1.0"` | IPVM job version | Yes | | -| `requestor` | DID | Requestor's DID | Yes | | -| `nonce` | String | Unique nonce | Yes | | -| `verification` | `{"optimistic": 2} or null` | | Yes | | -| `meta` | `&Object` | User-defined object (tags, comments, etc) | No | `{}` | -| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | -| `defaults` | `IpvmConfig` | | No | `{}` | -| `tasks` | `{String => Task}` | Named tasks | Yes | | -| `exception` | `Task.Wasm` | | No | `null` | -| `signature` | Varsig | Signature of serialized fields | Yes | | +| `type` | `"ipvm/workflow"` | Object type identifier | Yes | | +| `version` | `"0.1.0"` | IPVM workflow version | Yes | | +| `requestor` | DID | Requestor's DID | Yes | | +| `nonce` | String | Unique nonce | Yes | | +| `verification` | `{"optimistic": 2} or null` | | Yes | | +| `meta` | `&Object` | User-defined object (tags, comments, etc) | No | `{}` | +| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | +| `defaults` | `IpvmConfig` | | No | `{}` | +| `tasks` | `{String => Task}` | Named tasks | Yes | | +| `exception` | `Task.Wasm` | | No | `null` | +| `signature` | Varsig | Signature of serialized fields | Yes | | ``` ipldsch FIXME -type Job struct { +type Workflow struct { ver String req DID nnc String @@ -115,15 +115,15 @@ type OptimisticVerification struct { ## 2.1.1 Type -The `type` field MUST be set to `ipvm/job`. This field together with the `version` field indicates the expected fields and minimal semantics for the job. +The `type` field MUST be set to `ipvm/workflow`. This field together with the `version` field indicates the expected fields and minimal semantics for the workflow. ## 2.1.2 Version -The `version` field MUST be set to `0.1.0`. This field together with the `type` field indicates the expected fields and minimal semantics for the job. +The `version` field MUST be set to `0.1.0`. This field together with the `type` field indicates the expected fields and minimal semantics for the workflow. ## 2.1.3 Requestor -The `requestor` field MUST be set to the DID of the agent requesting the job. The Rquestor is the only identified agent in a Job. +The `requestor` field MUST be set to the DID of the agent requesting the workflow. The Rquestor is the only identified agent in a Workflow. The `signature` field MUST validate with a public key associated with the Requestor's DID. @@ -143,11 +143,11 @@ The `meta` field contains a user-definable JSON object. This is useful for inclu ## 2.1.7 Defaults -The global `defaults` object (FIXME section X.Y) sets the configuration for the job itself, and defaults for tasks. +The global `defaults` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. ## 2.1.8 Run -The `run` field contains all of the IPVM Tasks set to run in this Job, each labelled by a human-readable key. +The `run` field contains all of the IPVM Tasks set to run in this Workflow, each labelled by a human-readable key. See the [Task](FIXME) section for more. @@ -161,7 +161,7 @@ The signature of the CID represented by the other fields. ## 2.2 Example -Here is a nontrivial example of two tasks (`left` and `right`) used as input to a third task (`end`). Pictorally: +Here is a simple example: ``` ┌─────────┐ ┌─────────┐ @@ -178,11 +178,11 @@ Here is a nontrivial example of two tasks (`left` and `right`) used as input to └─────────┘ ``` -This is fully configured as: +Here, two tasks (`left` and `right`) are used as input to a third task (`end`). This is fully configured in IPVM as: ``` json { - "type": "ipvm/job", + "type": "ipvm/workflow", "version": "0.1.0", "requestor": "did:key:zAlice", "nonce": "o3--8Gdu5", @@ -193,7 +193,7 @@ This is fully configured as: "version": "0.1.0", "wasm": "bafkreiecadaahndb55cgvemhctwoojcc4hv4alogybpqndzj4mq7brixcy", "inputs": [], - "outputs": ["foo", "bar"] + "outputs": ["foo", "bar"] }, "right": { "type": "ipvm/wasm", @@ -218,7 +218,7 @@ This is fully configured as: # 3 Global Defaults -The global defaults object contains options for the Job itself, as well as cascading defaults for Tasks. +The global defaults object contains options for the Workflow itself, as well as cascading defaults for Tasks. | Field | Type | Description | Required | Default | |-----------|--------------|------------------------------|----------|---------| @@ -232,46 +232,18 @@ The global defaults object contains options for the Job itself, as well as casca # 4 Tasks -While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a job spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. +While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a workflow spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. +For more detail, refer to the [Task](FIXME) spec -| Field | Type | Description | Required | Default | -|-----------|-----------|------------------------------|----------|-----------| -| `type` | `string` | The type of task (Wasm, etc) | Yes | | -| `version` | SemVer | | No | `"0.1.0"` | -| `auth` | `&UCAN[]` | | No | `[]` | -| `secret` | `Boolean` | | No | `False` | -| `meta` | `Object` | | No | `{}` | - -## 4.1 Fields - -### 4.1.1 `type` - -The `type` field is used to declare the shape of the objet. This field MUST be either `ipvm/wasm` for pure Wasm, or `ipvm/effect` for effectful computation. - -### 4.1.2 `with` Resource - -The `with` field MUST contain a CID or URI of the resource to interact with. For example, this MAY be the Wasm to execute, or the URL of a web server to send a message to. - -### 4.1.3 `args` - -FIXME define mapping to ABI / WIT - -The `input` field contains an array of objects. Each entry represents an association of human-readable labels to values (or references to values). The index is significant, since many tasks take only positonal input. - -Values MUST be serialized as ______. If an input is given as an object, it MUST be treated as - -For ex - -## 4.2 Pipelining +## 4.1 Pipelining The output of one task is often the input to another. This is called pipelining, and MUST form one or more directed acyclic graphs (DAGs). Graphs MAY be unrooted. -For example, this is a legal set of task graphs in a single job. +For example, this is a legal set of task graphs in a single workflow. ``` -┌──────────────────Job Tasks──────────────────┐ +┌───────────────Workflow Tasks────────────────┐ │ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ @@ -306,88 +278,22 @@ Pipelining is acheived via dataflow. Every task is given a name, and its output( {"from": "bafkreifovuswf6ss7czm5gk6ibnd7klhoojhynmiydj6cf7p2yjdsevlga", "out": "firstName" } ``` -References by CID MAY be tasks that executed outside of the current job. If the task was not yet executed, it MUST NOT run inside this Job. - -# 5 Pure Wasm - -Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly availabel via WASI or similar. - -Since - -The Wasm configuration MUST extend the core task type with the following fields: - -| Field | Type | Description | Required | Default | -|-----------|---------------------|----------------------------------|----------|-------------------------------| -| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | -| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | -| `run` | CID | Reference to the Wasm to run | Yes | | -| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `secret` | Boolean | | No | `False` | -| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | - -## 5.1 `type` - -The `type` field declares this object to be an IPVM Wasm configuration. The value MUST be `ipvm/wasm`. - -## 5.2 `with` - -The `with` field declares the Wasm module to load via CID. - -Note that the - -## 5.3 `input` - -## 4.4 `maxgas` - -The `maxgas` field specifies the upper limit in gas that this task may consume. - -For the gas schedule, please see the [gas schedule spec]. - -# 6 Effects - -The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. - -The `with` field MAY be filled from a relative value (previous step) - -| Field | Type | Description | Required | Default | -|---------------|-----------------|---------------------------------------|----------|---------| -| `type` | `"ipvm/effect"` | Identify this job as an effect | Yes | | -| `version` | SemVer | IPVM effect schema version | No | `0.1.0` | -| `to` | URI | | Yes | | -| `do` | ability | | Yes | | -| `args` | `[{}]` | | No | `[]` | -| `timeout` | Integer | Timeout in milliseconds | No | `5000` | -| `auth` | `[&UCAN]` | | No | `[]` | - - - - -``` json -{ - "type": "ipvm/effect", - "to": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", - "do": "crypto/sign", - "args": [ - { "value": { "from": "earlierStep" } } - ] -} -``` +References by CID MAY be tasks that executed outside of the current workflow. If the task was not yet executed, it MUST NOT run inside this Workflow. -## 6.1 `type` # 7 Exception Handler Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. -It is often desirable to fire a specific job in the case that a job fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). +It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). -Each task MAY include a failure job to run on failure. +Each task MAY include a failure workflow to run on failure. -Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the job that caused the exception to be thrown. Running a pure value is RECOMMENDED. +Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. # 8 Related Work and Prior Art -AWS Lambda job specs +AWS Lambda workflow specs OCI E Language CapNet From 27e75c1578b5102574793fd784c21d011e992bda Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 23 Nov 2022 22:53:47 -0800 Subject: [PATCH 16/42] Task spec --- task/README.md | 114 +++++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/task/README.md b/task/README.md index 560cdde..918cc03 100644 --- a/task/README.md +++ b/task/README.md @@ -36,43 +36,43 @@ One way to represent the difference between these pictorally is with box-and-wir ``` Safe - │ ┌─ ─┐ - │ │ ┌─────────────┐ │ - │ │ │ │ │ - │ │ │ │ │ - │ Pure │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ └─ │ - │ │ - │ │ - │ │ Nondestructive - │ │ - │ │ - │ ┌─ │ - │ │ │ ┌─────────────┐ │ - │ │ └───► │ │ - │ │ │ │ │ - │ │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ │ ─┘ + │ ┌─ ─┐ + │ │ ┌─────────────┐ │ + │ │ │ │ │ + │ │ │ │ │ + │ Pure │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ └─ │ + │ │ + │ │ + │ │ Nondestructive + │ │ + │ │ + │ ┌─ │ + │ │ │ ┌─────────────┐ │ + │ │ └───► │ │ + │ │ │ │ │ + │ │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ │ ─┘ │ │ │ │ │ Effectful │ │ │ │ │ - │ │ ▲ ─┐ - │ │ ┌─────────────┐ │ │ - │ │ │ ├──┘ │ - │ │ │ │ │ - │ │ ──────► ├────► │ Destructive - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - ▼ └─ ─┘ + │ │ ▲ ─┐ + │ │ ┌─────────────┐ │ │ + │ │ │ ├──┘ │ + │ │ │ │ │ + │ │ ──────► ├────► │ Destructive + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + ▼ └─ ─┘ Unsafe ``` @@ -92,16 +92,15 @@ Destructive effects are the opposite: they "update the world". Sending an text m # 2 Envelope - All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. -| Field | Type | Description | Required | Default | -|-----------|-----------|------------------------------|----------|-----------| -| `type` | `string` | The type of task (Wasm, etc) | Yes | | -| `version` | SemVer | | No | `"0.1.0"` | -| `auth` | `&UCAN[]` | | No | `[]` | -| `secret` | `Boolean` | | No | `False` | -| `meta` | `Object` | | No | `{}` | +| Field | Type | Description | Required | Default | +|-----------|-------------------|------------------------------|----------|-----------| +| `type` | `string` | The type of task (Wasm, etc) | Yes | | +| `version` | SemVer | | No | `"0.1.0"` | +| `auth` | `&UCAN[]` | | No | `[]` | +| `secret` | `Boolean or null` | | No | `null` | +| `meta` | `Object` | | No | `{}` | ## 2.1 Fields @@ -123,25 +122,31 @@ Values MUST be serialized as ______. If an input is given as an object, it MUST For ex +### 2.1.4 `secret` + +The `secret` flag marks a task as being unsuitable for publication. +If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. +Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. # 3 Pure Wasm -Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly availabel via WASI or similar. +Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly available via WASI or similar aside from the ability to read content addressed data. + +Note that while the function itself is pure, as is dereferencing content-addressed data, the function MAY fail if the CID is not available to the runner. -Since - -The Wasm configuration MUST extend the core task type with the following fields: +The Wasm configuration MUST extend the core task type as follows: -| Field | Type | Description | Required | Default | -|-----------|---------------------|----------------------------------|----------|-------------------------------| -| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | -| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | -| `run` | CID | Reference to the Wasm to run | Yes | | -| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `secret` | Boolean | | No | `False` | -| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | +| Field | Type | Description | Required | Default | +|-----------|-----------------------|-------------------------------------------|----------|-------------------------------| +| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | +| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | +| `mod` | CID | Reference to the Wasm module to run | Yes | | +| `fun` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | +| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `secret` | Boolean | | No | `False` | +| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | ## 3.1 `type` @@ -192,3 +197,8 @@ The `with` field MAY be filled from a relative value (previous step) ``` ## 4.1 `type` + + +# 5 Prior Art + +# 6 Acknowledgments From ba54b40938e4938138bf6539e4f3ceb6888b2695 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 23 Nov 2022 22:55:00 -0800 Subject: [PATCH 17/42] Spec collectio table --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cd44103..5d4ae52 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). +# Subspecs + +* [Workflow](./workflow/README.md) +* [Task](./task/README.md) +* [Effect](./effect/README.md) + # 0 Abstract IPVM From 7d7073f24b8bc1f97880b1884214a5e79162e95f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 23 Nov 2022 23:09:16 -0800 Subject: [PATCH 18/42] Add subspecs / roadmap --- README.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d4ae52..f5f5f9b 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,30 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # Subspecs -* [Workflow](./workflow/README.md) -* [Task](./task/README.md) -* [Effect](./effect/README.md) +* Invocation + * [Workflow](./workflow/README.md) + * [Task](./task/README.md) + * [Effect](./effect/README.md) +* Runtime + * Scheduler + * Execution +* Lifecycle + * Request + * Negotiation + * Capabilties + * SPKI + * OCapN + * Verification + * Payment Channels +* Wasm μKernel + * IPFS + * Atomics and STM + * Actors +* First-Class Effects + * Randomness + * HTTP + * FVM + * Bacalhau # 0 Abstract From 84c71a47061a5b2bbce27cbe334e0bd81e866b4c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 25 Nov 2022 00:22:05 -0800 Subject: [PATCH 19/42] Mocking up a (hopefully) better Docker --- README.md | 13 ++++++++----- task/README.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f5f5f9b..e5b61ff 100644 --- a/README.md +++ b/README.md @@ -16,25 +16,28 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # Subspecs -* Invocation +* Description Formats * [Workflow](./workflow/README.md) * [Task](./task/README.md) * [Effect](./effect/README.md) + * Invocation + * Receipt * Runtime - * Scheduler + * Distributed Scheduler + * Planner * Execution * Lifecycle * Request * Negotiation - * Capabilties + * Capabilty * SPKI * OCapN * Verification - * Payment Channels + * Payment Channel * Wasm μKernel * IPFS * Atomics and STM - * Actors + * Actor * First-Class Effects * Randomness * HTTP diff --git a/task/README.md b/task/README.md index 918cc03..93c5191 100644 --- a/task/README.md +++ b/task/README.md @@ -199,6 +199,45 @@ The `with` field MAY be filled from a relative value (previous step) ## 4.1 `type` +``` json +{ + "type": "ipvm/task", + "version": "0.1.0", + "using": "wasm:Qm12345" + "args": { + "fun": "add_one", + "args": [1, 2, 3], + "maxgas": 1024 + } +} +``` + +``` json +{ + "type": "ipvm/task", + "version": "0.1.0", + "using": "docker:Qm12345" + "meta": { + "annotations": [] + }, + "args": { + "resources": { + "ram": {"gb": 10} + }, + "inputs": [1, 2, 3], + "entry": "/", + "workdir": "/", + "env": { + "$FOO": "bar" + }, + "timeout": {"seconds": "3600"}, + "contexts": [], + "output": [], + "sharding": 5 + } +} +``` + # 5 Prior Art # 6 Acknowledgments From 31b1142c8b518ca944fb5017fa476cf1194f01b7 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 25 Nov 2022 10:31:46 -0800 Subject: [PATCH 20/42] CHa! --- README.md | 4 ++ task/README.md | 117 ++++++++++++++++++++++++++++----------------- workflow/README.md | 1 + 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e5b61ff..a153e21 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,10 @@ An IPVM "job" is a declarative description of WebAssembly and managed effects to IPVM provides a deterministic-by-default, content addressed execution environment. Execution may always be run locally, but there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. +> Because he was talking (mainly) to a set of platform folks he admonished us to think about how we can build platforms that lead developers to write great, high performance code such that developers just fall into doing the “right thing”. Rico called this the Pit of Success. +> +> — Brad Abrams, [The Pit of Success](https://learn.microsoft.com/en-us/archive/blogs/brada/the-pit-of-success) + ## 1.1 Minimizing Complexity > Every application has an inherent amount of irreducible complexity. The only question is: Who will have to deal with it — the user, the application developer, or the platform developer? diff --git a/task/README.md b/task/README.md index 93c5191..8a2f251 100644 --- a/task/README.md +++ b/task/README.md @@ -7,7 +7,8 @@ ## Authors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) -* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) _(TODO: Provisionally!)_ +* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) +* [Luke Marsden](https://github.com/lukemarsden), [Bacalhau Project](https://www.bacalhau.org/) ## Language @@ -15,7 +16,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -Tasks are the smallest unit of work in an IPVM workflow. Each Task is restricted to a single type, such as Wasm or effects like an HTTP `GET` request. +Tasks are the smallest unit of work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. # 1 Introduction @@ -28,7 +29,7 @@ Tasks can be broken into categories: * Destructive * Nondestructive -Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from turning energy into heat). +Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from [heat](https://en.wikipedia.org/wiki/Second_law_of_thermodynamics)). Effects interact with the world in some way . Reading from a database, sending an email, and firing missiles are all kinds of effect. @@ -36,43 +37,43 @@ One way to represent the difference between these pictorally is with box-and-wir ``` Safe - │ ┌─ ─┐ - │ │ ┌─────────────┐ │ - │ │ │ │ │ - │ │ │ │ │ - │ Pure │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ └─ │ - │ │ - │ │ - │ │ Nondestructive - │ │ - │ │ - │ ┌─ │ - │ │ │ ┌─────────────┐ │ - │ │ └───► │ │ - │ │ │ │ │ - │ │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ │ ─┘ - │ │ - │ │ - │ Effectful │ - │ │ - │ │ - │ │ ▲ ─┐ - │ │ ┌─────────────┐ │ │ - │ │ │ ├──┘ │ - │ │ │ │ │ - │ │ ──────► ├────► │ Destructive - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - ▼ └─ ─┘ + │ ┌─ ─┐ + │ │ ┌─────────────┐ │ + │ │ │ │ │ + │ │ │ │ │ + │ Pure │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ └─ │ + │ │ + │ │ + │ │ Nondestructive + │ │ + │ │ + │ ┌─ ┌─ │ + │ │ │ │ ┌─────────────┐ │ + │ │ │ └───► │ │ + │ │ │ │ │ │ + │ │ Query │ ──────► ├────► │ + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ └─────────────┘ │ + │ │ └─ ─┘ + │ │ + │ │ + │ Effectful │ + │ │ + │ │ + │ │ ┌─ ▲ ─┐ + │ │ │ ┌─────────────┐ │ │ + │ │ │ │ ├──┘ │ + │ │ │ │ │ │ + │ │ Command │ ──────► ├────► │ Destructive + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ └─────────────┘ │ + ▼ └─ └─ ─┘ Unsafe ``` @@ -80,6 +81,8 @@ Unsafe Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. +Note that in IPVM, pre-resolved CID handles are treated as referentially transparent. See [CID Handles](FIXME). + ## 1.2 Nondestructive Effects For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. @@ -90,6 +93,29 @@ Nondestructive effects can be retried or raced safely. Each nondestructive invoc Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. +# 2 Content Handles + +[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. This however does not guarintee that + +This that the CID has been checked, and the runner guarintees that it is available in the current environment. + +A Content Handle (CHa) is a type that MUST only be created by the runtime. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. + +``` js +// Just a sketch for now, don't judge me! +{ + "type": "ipvm/effect", + "version": "0.1.0", + "using": "cid:Qm12345", + "do": "handle/resolve" +} +``` + +```haskell +-- No, this won't survive into the fnal draft. Just stashing it here for now as a note! +resolve :: CID -> IO (Either Timeout CHa) +``` + # 2 Envelope All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. @@ -201,10 +227,12 @@ The `with` field MAY be filled from a relative value (previous step) ``` json { - "type": "ipvm/task", + "type": "ipvm/pure", "version": "0.1.0", "using": "wasm:Qm12345" "args": { + "type": "ipvm/wasm", + "version": "0.1.0", "fun": "add_one", "args": [1, 2, 3], "maxgas": 1024 @@ -214,13 +242,14 @@ The `with` field MAY be filled from a relative value (previous step) ``` json { - "type": "ipvm/task", + "type": "ipvm/effect", "version": "0.1.0", "using": "docker:Qm12345" "meta": { - "annotations": [] + "description": "Tensorflow container", + "tags": ["machine-learning", "tensorflow", "myproject"] }, - "args": { + "do": { "resources": { "ram": {"gb": 10} }, diff --git a/workflow/README.md b/workflow/README.md index ac9903e..1085419 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -313,3 +313,4 @@ It is not possible to mention the separation of effects from computation without * Christine * Blaine Cook * Luke Marsden +* Quinn Wilton From 5acd8161939242642f6efafa6e99a4196d82377a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 25 Nov 2022 16:45:00 -0800 Subject: [PATCH 21/42] Start on invocations (stage after job request) --- README.md | 2 +- invocation/README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++ task/README.md | 47 +++++++++++++++-------- 3 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 invocation/README.md diff --git a/README.md b/README.md index a153e21..fd3f260 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * [Workflow](./workflow/README.md) * [Task](./task/README.md) * [Effect](./effect/README.md) - * Invocation + * [Invocation](./invocation/README.md) * Receipt * Runtime * Distributed Scheduler diff --git a/invocation/README.md b/invocation/README.md new file mode 100644 index 0000000..006e9a1 --- /dev/null +++ b/invocation/README.md @@ -0,0 +1,89 @@ +# IPVM Invocation Specification v0.1.0 + +## Editors + +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) + +## Authors + +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). + +# 0 Abstract + +An IPVM invocation is the container for everything required to run an IPVM Task. An invocation can be derived entirely from its linked capabilities. + +It specifies a resource and what action to perform with it, capability proof(s) for those actions, and ____. + +# 1 Introduction + +# 2 Format + +``` json +{ + "type": "ucan/invoke", + "caps": [ "Qm123", "Qm456" ], + "signature": 0xC0FFEE +} +``` + +Expanded: + +``` json +{ + "type": "ucan/invoke", + "run": { + "left": { + "cap": { + "iss": "did:key:zRequestor", + "aud": "did:key:zRunner", + "exp": 999999, + "att": { + "dns://foo.exmaple.com?TYPE=TXT": { + "crud/update": [ + { "to": "hello world" } + ], + "crud/read": [] + } + } + } + }, + "right": { + "cap": { + "iss": "did:key:zRequestor", + "aud": "did:key:zRunner", + "exp": 999999, + "att": { + "dns://foo.exmaple.com?type=txt": { + "crud/update": [ + { "to": "hello world" } + ], + "crud/read": [] + } + } + } + }, + "end": { + "cap": { + "iss": "did:key:zrequestor", + "aud": "did:key:zrunner", + "exp": 999999, + "att": { + "dns://foo.exmaple.com?type=txt": { + "crud/update": [ + { "to": "hello world" } + ], + "crud/read": [] + } + } + }, + "after": ["left", "right"] + } + }, + "siganture": 0xCOFFEE +} +``` + diff --git a/task/README.md b/task/README.md index 8a2f251..9bfa800 100644 --- a/task/README.md +++ b/task/README.md @@ -1,5 +1,9 @@ # IPVM Task Specification v0.1.0 +> With hands of iron, there's not a task we couldn't do +> +> The Good Doctor, [The Protomen](https://en.wikipedia.org/wiki/The_Protomen) + ## Editors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) @@ -8,7 +12,6 @@ * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) * [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) -* [Luke Marsden](https://github.com/lukemarsden), [Bacalhau Project](https://www.bacalhau.org/) ## Language @@ -16,11 +19,11 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -Tasks are the smallest unit of work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. +Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. # 1 Introduction -Tasks describe everything required to execute the job. While all Tasks share some things in common, the details MAY be quite different. +Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. Tasks can be broken into categories: @@ -37,15 +40,15 @@ One way to represent the difference between these pictorally is with box-and-wir ``` Safe - │ ┌─ ─┐ - │ │ ┌─────────────┐ │ - │ │ │ │ │ - │ │ │ │ │ - │ Pure │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ └─ │ + ▲ ┌─ ─┐ + │ │ ┌─────────────┐ │ + │ │ │ │ │ + │ │ │ │ │ + │ Pure │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ └─ │ │ │ │ │ │ │ Nondestructive @@ -95,11 +98,17 @@ Destructive effects are the opposite: they "update the world". Sending an text m # 2 Content Handles -[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. This however does not guarintee that +[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. However, this does not guarantee that the CID is resolvable at a particular time or place. + +A Content Handle (CHa) is a type that MUST only be created by the runtime and MUST NOT have a serializated representation. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. This that the CID has been checked, and the runner guarintees that it is available in the current environment. -A Content Handle (CHa) is a type that MUST only be created by the runtime. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. +| Issue | At Fault | +|-------------------------------|------------------------| +| Malformed CID | Requestor | +| Cannot provide all CHa blocks | Runner | +| Cannot resolve CID to CHa | Network or Environment | ``` js // Just a sketch for now, don't judge me! @@ -124,10 +133,15 @@ All tasks MUST contain at least the following fields. They MAY contain others, d |-----------|-------------------|------------------------------|----------|-----------| | `type` | `string` | The type of task (Wasm, etc) | Yes | | | `version` | SemVer | | No | `"0.1.0"` | -| `auth` | `&UCAN[]` | | No | `[]` | | `secret` | `Boolean or null` | | No | `null` | | `meta` | `Object` | | No | `{}` | + + + + ## 2.1 Fields ### 2.1.1 `type` @@ -225,7 +239,7 @@ The `with` field MAY be filled from a relative value (previous step) ## 4.1 `type` -``` json + ``` json { "type": "ipvm/pure", "version": "0.1.0", @@ -249,6 +263,7 @@ The `with` field MAY be filled from a relative value (previous step) "description": "Tensorflow container", "tags": ["machine-learning", "tensorflow", "myproject"] }, + "after": ["previousStep", "QmXYZ"] // Contraint on effect ordering, as opposed to using the inputs directly "do": { "resources": { "ram": {"gb": 10} From be735baab803a330f5ca604e9e5484c54c28e8d5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 15:58:51 -0800 Subject: [PATCH 22/42] Forgot to push last week --- README.md | 2 +- invocation/README.md | 13 +++++++++++++ task/README.md | 3 +++ workflow/README.md | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd3f260..1127b75 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ * [Blaine Cook](https://github.com/blaine), [Fission](https://fission.codes) * [Zeeshan Lakhani](https://github.com/zeeshanlakhani), [Fission](https://fission.codes) -* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) +* [ ] [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) ## Language diff --git a/invocation/README.md b/invocation/README.md index 006e9a1..62d1b5b 100644 --- a/invocation/README.md +++ b/invocation/README.md @@ -87,3 +87,16 @@ Expanded: } ``` +but in actuality, this will look like: + +``` json +{ + "ucan/invoke": [ + {"ucan": "bafyRight"}, + {"ucan": "bafyLeft"}, + {"ucan": "bafyEnd", "after": ["bafyLeft", "bafyRight"]} + ] + "siganture": 0xCOFFEE +} +``` + diff --git a/task/README.md b/task/README.md index 9bfa800..3d23ecf 100644 --- a/task/README.md +++ b/task/README.md @@ -285,3 +285,6 @@ The `with` field MAY be filled from a relative value (previous step) # 5 Prior Art # 6 Acknowledgments + + +NOTE TO SELFL inputs as "ports" diff --git a/workflow/README.md b/workflow/README.md index 1085419..27e705e 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -1,5 +1,9 @@ # IPVM Workflow Specification v0.1.0 +> In late 1970 or early ’71 I approached IBM Canada’s Intellectual Property department to see if we could take out a patent on the basic idea [of dataflow]. Their recommendation, which I feel was prescient, was that this concept seemed to them more like a law of nature, which is not patentable. +> +> J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) + ## Editors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) From 089ef46ff1943066806a2f28aba4e844f7ce1d6d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 16:16:00 -0800 Subject: [PATCH 23/42] Explicitely link out to UCAN invcoations/actions --- README.md | 40 +++++++++++++++-- invocation/README.md | 102 ------------------------------------------- task/README.md | 66 +++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 108 deletions(-) delete mode 100644 invocation/README.md diff --git a/README.md b/README.md index 1127b75..83c3317 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,25 @@ * [Blaine Cook](https://github.com/blaine), [Fission](https://fission.codes) * [Zeeshan Lakhani](https://github.com/zeeshanlakhani), [Fission](https://fission.codes) -* [ ] [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) +* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) ## Language The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). -# Subspecs +## Depends On + +* [Multiformats](https://multiformats.io) +* [UCAN Capabilities](https://github.com/ucan-wg/spec) +* [UCAN Invocation](https://github.com/ucan-wg/invocation) + +## Subspecs * Description Formats * [Workflow](./workflow/README.md) * [Task](./task/README.md) * [Effect](./effect/README.md) - * [Invocation](./invocation/README.md) - * Receipt + * [UCAN Invocation](https://github.com/ucan-wg/invocation) * Runtime * Distributed Scheduler * Planner @@ -71,6 +76,33 @@ By having to account for a huge number of possible cases, the burden is placed o Partial failure in a deterministic system is simplified by using transactional semantics for the job as a whole. The difficult case lies with any effects that destructively update the real world. +# Stack Diagrram + +``` +┌───────────────────────────────────────────────┬───────────────────────────┐ +│ │ │ +│ Human Configuration: │ │ +│ Defaults, Exception Handling, Comments, Tags │ │ +│ (IPVM Workflow) │ │ +│ │ Multi-Request Pipelining │ +├───────────────────────────────────────────────┤ (UCAN Invocation) │ +│ │ │ +│ IPVM Config, Verification Level, etc │ │ +│ (IPVM Task) │ │ +│ │ │ +├───────────────────────────────────────────────┴───────────────────────────┤ +│ │ +│ Call Graph │ +│ (UCAN Invocation) │ +│ │ +├───────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Authority │ +│ (UCAN Core) │ +│ │ +└───────────────────────────────────────────────────────────────────────────┘ +``` + # 2 Effect System ## 2.1 Pure Functions diff --git a/invocation/README.md b/invocation/README.md deleted file mode 100644 index 62d1b5b..0000000 --- a/invocation/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# IPVM Invocation Specification v0.1.0 - -## Editors - -* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) - -## Authors - -* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) - -## Language - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). - -# 0 Abstract - -An IPVM invocation is the container for everything required to run an IPVM Task. An invocation can be derived entirely from its linked capabilities. - -It specifies a resource and what action to perform with it, capability proof(s) for those actions, and ____. - -# 1 Introduction - -# 2 Format - -``` json -{ - "type": "ucan/invoke", - "caps": [ "Qm123", "Qm456" ], - "signature": 0xC0FFEE -} -``` - -Expanded: - -``` json -{ - "type": "ucan/invoke", - "run": { - "left": { - "cap": { - "iss": "did:key:zRequestor", - "aud": "did:key:zRunner", - "exp": 999999, - "att": { - "dns://foo.exmaple.com?TYPE=TXT": { - "crud/update": [ - { "to": "hello world" } - ], - "crud/read": [] - } - } - } - }, - "right": { - "cap": { - "iss": "did:key:zRequestor", - "aud": "did:key:zRunner", - "exp": 999999, - "att": { - "dns://foo.exmaple.com?type=txt": { - "crud/update": [ - { "to": "hello world" } - ], - "crud/read": [] - } - } - } - }, - "end": { - "cap": { - "iss": "did:key:zrequestor", - "aud": "did:key:zrunner", - "exp": 999999, - "att": { - "dns://foo.exmaple.com?type=txt": { - "crud/update": [ - { "to": "hello world" } - ], - "crud/read": [] - } - } - }, - "after": ["left", "right"] - } - }, - "siganture": 0xCOFFEE -} -``` - -but in actuality, this will look like: - -``` json -{ - "ucan/invoke": [ - {"ucan": "bafyRight"}, - {"ucan": "bafyLeft"}, - {"ucan": "bafyEnd", "after": ["bafyLeft", "bafyRight"]} - ] - "siganture": 0xCOFFEE -} -``` - diff --git a/task/README.md b/task/README.md index 3d23ecf..3ee6a80 100644 --- a/task/README.md +++ b/task/README.md @@ -23,6 +23,8 @@ Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is # 1 Introduction +IPVM Tasks are a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Tasks require certain fields in the `inputs` field. + Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. Tasks can be broken into categories: @@ -104,6 +106,8 @@ A Content Handle (CHa) is a type that MUST only be created by the runtime and MU This that the CID has been checked, and the runner guarintees that it is available in the current environment. +NOTE TO SELF: should CHa be its own spec? Seems useful :thinking: + | Issue | At Fault | |-------------------------------|------------------------| | Malformed CID | Requestor | @@ -142,6 +146,65 @@ All tasks MUST contain at least the following fields. They MAY contain others, d Why no auth at this layer? Really simple: you don't know who you're delegating to yet! --> +``` json +{ + "ucan/invoke": "QmYW8Z58V1v8R25USVPUuFHtU7nGouApdGTk3vRPXmVHPR", + "v": "0.1.0", + "nnc": "abcdef", + "ext": null, + "run": { + "update-dns" : { + "using": "dns://example.com?TYPE=TXT": + "do": "crud/update", + "inputs": { + "value": "hello world", + "retries": 5 + } + }, + "notify-bob": { + "using": "mailto://alice@example.com", + "do": "msg/send", + "inputs": [ + { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} + } + ] + }, + "notify-carol": { + "using": "mailto://alice@example.com", + "do": "msg/send", + "inputs": [ + { + "to": "carol@example.com", + "subject": "DNSLink for example.com", + "body": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} + } + ] + }, + "log-as-done": { + "using": "https://example.com/report" + "do": "crud/update" + "inputs": [ + { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification", + }, + { + "_": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} + }, + { + "_": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} + } + ] + } + }, + "sig": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" +} +``` + ## 2.1 Fields ### 2.1.1 `type` @@ -241,9 +304,8 @@ The `with` field MAY be filled from a relative value (previous step) ``` json { - "type": "ipvm/pure", - "version": "0.1.0", "using": "wasm:Qm12345" + "do": "ipvm/call", "args": { "type": "ipvm/wasm", "version": "0.1.0", From 2436eee1bc69b73f69dc01c0a3ad2a34e24ccf50 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 17:48:59 -0800 Subject: [PATCH 24/42] Envelope type --- task/README.md | 168 ++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 86 deletions(-) diff --git a/task/README.md b/task/README.md index 3ee6a80..02a0752 100644 --- a/task/README.md +++ b/task/README.md @@ -23,10 +23,12 @@ Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is # 1 Introduction -IPVM Tasks are a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Tasks require certain fields in the `inputs` field. - Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. +IPVM Tasks are defined as a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Tasks require certain fields in the `inputs` field to configure IPVM for timeouts, gas usage, credits, transactional guarantees, result visibility, and so on. + +## 2 Effects + Tasks can be broken into categories: 1. Pure @@ -82,23 +84,27 @@ One way to represent the difference between these pictorally is with box-and-wir Unsafe ``` -## 1.1 Pure Functions +FIXME safety level MUST be defined by the pair `(URI Scheme, Ability)` (service metadata). This may need a field on the workflow. + +## 2.1 Pure Functions Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. Note that in IPVM, pre-resolved CID handles are treated as referentially transparent. See [CID Handles](FIXME). -## 1.2 Nondestructive Effects +## 2.2 Nondestructive Effects For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. Nondestructive effects can be retried or raced safely. Each nondestructive invocation is unique, and need to be attested from a trusted source. Once their value enters the IPVM system, it is treated as a pure value. -## 1.3 Destructive Effects +## 2.3 Destructive Effects Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. -# 2 Content Handles +# 3 Content Handles + +FIXME This probably belongs in its own spec. Now that we have the basic concept, it keeps coming up in conversation. [Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. However, this does not guarantee that the CID is resolvable at a particular time or place. @@ -131,101 +137,86 @@ resolve :: CID -> IO (Either Timeout CHa) # 2 Envelope -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. - -| Field | Type | Description | Required | Default | -|-----------|-------------------|------------------------------|----------|-----------| -| `type` | `string` | The type of task (Wasm, etc) | Yes | | -| `version` | SemVer | | No | `"0.1.0"` | -| `secret` | `Boolean or null` | | No | `null` | -| `meta` | `Object` | | No | `{}` | +An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. - +``` ipldsch +type Action struct { + using URI + do Ability + input Any + + -- IPVM Specific + config TaskConfig (implicit {}) +} - +type TaskConfig struct { + v SemVer + secret Boolean (implicit False) + check Verification +} -``` json -{ - "ucan/invoke": "QmYW8Z58V1v8R25USVPUuFHtU7nGouApdGTk3vRPXmVHPR", - "v": "0.1.0", - "nnc": "abcdef", - "ext": null, - "run": { - "update-dns" : { - "using": "dns://example.com?TYPE=TXT": - "do": "crud/update", - "inputs": { - "value": "hello world", - "retries": 5 - } - }, - "notify-bob": { - "using": "mailto://alice@example.com", - "do": "msg/send", - "inputs": [ - { - "to": "bob@example.com", - "subject": "DNSLink for example.com", - "body": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} - } - ] - }, - "notify-carol": { - "using": "mailto://alice@example.com", - "do": "msg/send", - "inputs": [ - { - "to": "carol@example.com", - "subject": "DNSLink for example.com", - "body": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} - } - ] - }, - "log-as-done": { - "using": "https://example.com/report" - "do": "crud/update" - "inputs": [ - { - "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com", "carol@example.com"], - "event": "email-notification", - }, - { - "_": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} - }, - { - "_": {"ucan/promise": ["/", "dns://example.com?TYPE=TXT", "crud/update", "http/body"]} - } - ] - } - }, - "sig": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" +type Verification struct { + | Attestation + | Consensus(Integer) + | Optimistic(Integer) + | ZKP(ZeroKnowledge) } -``` -## 2.1 Fields +type Optimistic struct { + confirmations Integer + referee Referee +} -### 2.1.1 `type` +type Referee enum { + | ZK(ZeroKnowledge) + | Trusted(URI) +} -The `type` field is used to declare the shape of the objet. This field MUST be either `ipvm/wasm` for pure Wasm, or `ipvm/effect` for effectful computation. +type ZeroKnowledge enum { + | Groth16 + | Nova + | Nova2 +} +``` -### 2.1.2 `with` Resource +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. -The `with` field MUST contain a CID or URI of the resource to interact with. For example, this MAY be the Wasm to execute, or the URL of a web server to send a message to. +| Field | Type | Description | Required | Default | +|----------|------------------------|-----------------------------------------|----------|---------| +| `v` | SemVer | IPVM Task Version | Yes | | +| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | +| `check` | `Verification or null` | | | | -### 2.1.3 `args` +## 2.1 IPLD Schema -FIXME define mapping to ABI / WIT +``` ipldsch +type TaskConfig struct { + secret nullable optional Boolean +} +``` -The `input` field contains an array of objects. Each entry represents an association of human-readable labels to values (or references to values). The index is significant, since many tasks take only positonal input. +## 2.2 JSON Examples -Values MUST be serialized as ______. If an input is given as an object, it MUST be treated as +``` json +{ + "using": "dns://example.com?TYPE=TXT", + "do": "crud/update", + "inputs": { + "value": "hello world" + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "timeout": { "ms": 5000 }, + "retries": 5, + "verification": + } +} +``` -For ex +## 2.1 Fields -### 2.1.4 `secret` +### 2.1.1 `secret` The `secret` flag marks a task as being unsuitable for publication. @@ -346,6 +337,11 @@ The `with` field MAY be filled from a relative value (previous step) # 5 Prior Art +* [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) +* [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) +* [UCAN Invocation](https://github.com/ucan-wg/invocation) +* [WarpForge Formula](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) + # 6 Acknowledgments From 5e67ddd2d06e2d3b929fa1843e05b6125220ae26 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 19:40:09 -0800 Subject: [PATCH 25/42] Start tightening up and splitting out stuff that's not diretly about a Task --- task/README.md | 172 +++++++++++++++++++++++++++++-------------------- 1 file changed, 102 insertions(+), 70 deletions(-) diff --git a/task/README.md b/task/README.md index 02a0752..cf36677 100644 --- a/task/README.md +++ b/task/README.md @@ -2,7 +2,7 @@ > With hands of iron, there's not a task we couldn't do > -> The Good Doctor, [The Protomen](https://en.wikipedia.org/wiki/The_Protomen) +> — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor ## Editors @@ -141,25 +141,61 @@ An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan- ``` ipldsch type Action struct { + -- UCAN Invoctaion using URI do Ability input Any - -- IPVM Specific + -- IPVM Configuration config TaskConfig (implicit {}) } +``` + +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. + +| Field | Type | Description | Required | Default | +|----------|-------------------|-----------------------------------------|----------|-----------------| +| `v` | SemVer | IPVM Task Version | Yes | | +| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | +| `check` | `Verification` | How to verify the output | No | `"attestation"` | + +## 2.1 Fields + +### 2.1.1 Version + +The version of the IPVM + +### 2.1.2 Secret Flag + +The `secret` flag marks a task as being unsuitable for publication. + +If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. + +Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. + +### 2.1.3 Verification Method +FIXME + +## 2.2 IPLD Schema + +``` ipldsch type TaskConfig struct { v SemVer secret Boolean (implicit False) - check Verification + check Verification (implicit Attestation) } -type Verification struct { - | Attestation +type Verification enum { + | Oracle | Consensus(Integer) | Optimistic(Integer) - | ZKP(ZeroKnowledge) + | ZKP +} + +type Oracle enum { + | Attestation + | ThirdParty(DID) } type Optimistic struct { @@ -172,58 +208,36 @@ type Referee enum { | Trusted(URI) } -type ZeroKnowledge enum { +type Consensus struct { + agents [DID] + -- FIXME needs more detail on method etc +} + +type ZKP enum { | Groth16 | Nova | Nova2 -} ``` -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. - -| Field | Type | Description | Required | Default | -|----------|------------------------|-----------------------------------------|----------|---------| -| `v` | SemVer | IPVM Task Version | Yes | | -| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification or null` | | | | - -## 2.1 IPLD Schema - -``` ipldsch -type TaskConfig struct { - secret nullable optional Boolean -} -``` - -## 2.2 JSON Examples +## 2.3 JSON Examples ``` json { "using": "dns://example.com?TYPE=TXT", "do": "crud/update", "inputs": { - "value": "hello world" + "value": "hello world" }, "ipvm/config": { "v": "0.1.0", "secret": false, "timeout": { "ms": 5000 }, "retries": 5, - "verification": + "verification": "attestation" } } ``` -## 2.1 Fields - -### 2.1.1 `secret` - -The `secret` flag marks a task as being unsuitable for publication. - -If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. - -Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. - # 3 Pure Wasm Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly available via WASI or similar aside from the ability to read content addressed data. @@ -232,33 +246,55 @@ Note that while the function itself is pure, as is dereferencing content-address The Wasm configuration MUST extend the core task type as follows: -| Field | Type | Description | Required | Default | -|-----------|-----------------------|-------------------------------------------|----------|-------------------------------| -| `type` | `"ipvm/wasm"` | Identify this task as Wasm 1.0 | Yes | | -| `version` | SemVer | The Wasm module's Wasm version | No | `"0.1.0"` | -| `mod` | CID | Reference to the Wasm module to run | Yes | | -| `fun` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | -| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | -| `secret` | Boolean | | No | `False` | -| `maxgas` | Integer | Maximum gas for the invocation | No | 1000 | - -## 3.1 `type` - -The `type` field declares this object to be an IPVM Wasm configuration. The value MUST be `ipvm/wasm`. - -## 3.2 `with` - -The `with` field declares the Wasm module to load via CID. - -Note that the - -## 3.3 `input` +| Field | Type | Description | Required | Default | +|--------|-----------------------|-------------------------------------------|----------|---------| +| `v` | SemVer | The Wasm module's Wasm version | No | `0.1.0` | +| `func` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | +| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | + +## 3.2 IPLDS Schema -## 3.4 `maxgas` +``` ipldsch +type WasmTask struct { + v SemVer + func String -- Function name to invoke + args [Any] -- Positional arguments FIXME **for now** -- we need to figure out WIT, I know +} +``` -The `maxgas` field specifies the upper limit in gas that this task may consume. +## 3.3 JSON Example -For the gas schedule, please see the [gas schedule spec]. +``` js +{ + // Clean, but possible a bridge too far. Probably handle this in an implcit like CIDs + "supply-gas": { + "using": "gas:reserve://mine", // Or something... needs work at least + "do": "gas/supply", + "inputs": { + "on": ["/", "some-wasm"], + "max": 1000 + } + }, + "some-wasm": { + "using": "wasm:1:Qm12345", // Or something... wasm:Qm12345? + "do": "wasm/run", + "inputs": { + "func": "calculate", + "args": [ + 1, + "hello world", + {"ucan/promise": ["/", "some-other-action" /* ... */]}, + {"a": 1, "b": 2, "c": 3} + ] + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": {"optimistic": 2} + } + } +} +``` # 4 Effects @@ -266,15 +302,11 @@ The contract for effects is different from pure computation. As effects by defin The `with` field MAY be filled from a relative value (previous step) -| Field | Type | Description | Required | Default | -|-----------|-----------------|-------------------------------------|----------|---------| -| `type` | `"ipvm/effect"` | Identify this workflow as an effect | Yes | | -| `version` | SemVer | IPVM effect schema version | No | `0.1.0` | -| `using` | URI | | Yes | | -| `do` | ability | | Yes | | -| `args` | `[{}]` | | No | `[]` | -| `timeout` | Integer | Timeout in milliseconds | No | `5000` | -| `auth` | `[&UCAN]` | | No | `[]` | +| Field | Type | Description | Required | Default | +|-----------|-----------|----------------------------|----------|---------| +| `v` | SemVer | IPVM effect schema version | No | `0.1.0` | +| `args` | `[{}]` | | No | `[]` | +| `timeout` | Integer | Timeout in milliseconds | No | `5000` | From 1cc8b18c3de23f61d3ee5aeca80568231631223c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 21:28:35 -0800 Subject: [PATCH 26/42] Simplify down Task, break out effect, rely more heavily on UCAN Invocation --- effect/README.md | 47 ++++++++++++++++++++++++ task/README.md | 94 +++++++++++++++--------------------------------- 2 files changed, 75 insertions(+), 66 deletions(-) diff --git a/effect/README.md b/effect/README.md index 3e2d6c4..fd99336 100644 --- a/effect/README.md +++ b/effect/README.md @@ -124,3 +124,50 @@ Randomness is RECOMMENDED to be souiderived from a trused high-entropy source, s NOTE TO SELF: on `crud/read`, we probably need some kind of max file size limit (and timeout obvs) + + + + + +# 4 Effects + +The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. + +The `with` field MAY be filled from a relative value (previous step) + +| Field | Type | Description | Required | Default | +|----------|---------|----------------------------|----------|---------| +| `v` | SemVer | IPVM effect schema version | No | `0.1.0` | +| `args` | `[{}]` | | No | `[]` | + + + +## 4.3 JSON Example + +``` json +{ + "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? + "do": "executable/run", + "inputs": { + "func": "calculate", + "args": [ + 1, + "hello world", + {"c": {"ucan/promise": ["/", "some-other-action"]}}, + {"a": 1, "b": 2, "c": 3} + ], + "container": { + "entry": "/", + "workdir": "/", + }, + "env": { + "$FOO": "bar" + } + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": {"optimistic": 2} + } +} +``` diff --git a/task/README.md b/task/README.md index cf36677..5dfa9fe 100644 --- a/task/README.md +++ b/task/README.md @@ -158,6 +158,9 @@ All tasks MUST contain at least the following fields. They MAY contain others, d | `v` | SemVer | IPVM Task Version | Yes | | | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | | `check` | `Verification` | How to verify the output | No | `"attestation"` | +| `time` | Integer | Timeout in milliseconds | No | `5000` | +| `memory` | Integer | Memory limit in KB | No | 1000 | +| `disk` | Integer | Disk limit in KB | No | 100000 | ## 2.1 Fields @@ -250,7 +253,7 @@ The Wasm configuration MUST extend the core task type as follows: |--------|-----------------------|-------------------------------------------|----------|---------| | `v` | SemVer | The Wasm module's Wasm version | No | `0.1.0` | | `func` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | -| `args` | `[{String => CID}]` | Arguments to the Wasm executable | Yes | | +| `args` | `[{String : Any}]` | Arguments to the Wasm executable | Yes | | ## 3.2 IPLDS Schema @@ -262,7 +265,9 @@ type WasmTask struct { } ``` -## 3.3 JSON Example +## 3.3 JSON Examples + +Deterministic WebAssembly ``` js { @@ -277,13 +282,13 @@ type WasmTask struct { }, "some-wasm": { "using": "wasm:1:Qm12345", // Or something... wasm:Qm12345? - "do": "wasm/run", + "do": "ipvm/run", "inputs": { "func": "calculate", "args": [ 1, "hello world", - {"ucan/promise": ["/", "some-other-action" /* ... */]}, + {"c": {"ucan/promise": ["/", "some-other-action"]}}, {"a": 1, "b": 2, "c": 3} ] }, @@ -296,73 +301,32 @@ type WasmTask struct { } ``` -# 4 Effects - -The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. - -The `with` field MAY be filled from a relative value (previous step) - -| Field | Type | Description | Required | Default | -|-----------|-----------|----------------------------|----------|---------| -| `v` | SemVer | IPVM effect schema version | No | `0.1.0` | -| `args` | `[{}]` | | No | `[]` | -| `timeout` | Integer | Timeout in milliseconds | No | `5000` | - - - +Docker ``` json { - "type": "ipvm/effect", - "to": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", - "do": "crypto/sign", + "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? + "do": "docker/run", + "inputs": { + "func": "calculate", "args": [ - { "value": { "from": "earlierStep" } } - ] -} -``` - -## 4.1 `type` - - - ``` json -{ - "using": "wasm:Qm12345" - "do": "ipvm/call", - "args": { - "type": "ipvm/wasm", - "version": "0.1.0", - "fun": "add_one", - "args": [1, 2, 3], - "maxgas": 1024 - } -} -``` - -``` json -{ - "type": "ipvm/effect", - "version": "0.1.0", - "using": "docker:Qm12345" - "meta": { - "description": "Tensorflow container", - "tags": ["machine-learning", "tensorflow", "myproject"] - }, - "after": ["previousStep", "QmXYZ"] // Contraint on effect ordering, as opposed to using the inputs directly - "do": { - "resources": { - "ram": {"gb": 10} + 1, + "hello world", + {"c": {"ucan/promise": ["/", "some-other-action"]}}, + {"a": 1, "b": 2, "c": 3} + ], + "container": { + "entry": "/", + "workdir": "/", }, - "inputs": [1, 2, 3], - "entry": "/", - "workdir": "/", "env": { "$FOO": "bar" - }, - "timeout": {"seconds": "3600"}, - "contexts": [], - "output": [], - "sharding": 5 + } + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": {"optimistic": 2} } } ``` @@ -376,5 +340,3 @@ The `with` field MAY be filled from a relative value (previous step) # 6 Acknowledgments - -NOTE TO SELFL inputs as "ports" From c85da365916d84762c43ad4f67b0007e34fe123d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 27 Nov 2022 22:47:34 -0800 Subject: [PATCH 27/42] Laying out new spec --- effect/README.md | 132 +++++++++++++++++ task/README.md | 342 -------------------------------------------- workflow/README.md | 349 +++++++++++++++++++++++++++------------------ 3 files changed, 344 insertions(+), 479 deletions(-) delete mode 100644 task/README.md diff --git a/effect/README.md b/effect/README.md index fd99336..f2c8e80 100644 --- a/effect/README.md +++ b/effect/README.md @@ -171,3 +171,135 @@ The `with` field MAY be filled from a relative value (previous step) } } ``` + +## 2 Effects + +Tasks can be broken into categories: + +1. Pure +2. Effectful + * Destructive + * Nondestructive + +Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from [heat](https://en.wikipedia.org/wiki/Second_law_of_thermodynamics)). + +Effects interact with the world in some way . Reading from a database, sending an email, and firing missiles are all kinds of effect. + +One way to represent the difference between these pictorally is with box-and-wire diagrams. Here computation is drawn as a box. Explicit input and output are drawn as horizontal arrows, with input on the left and output on the right. Effects are drawn as vertical-pointing arrows. + +``` + Safe + ▲ ┌─ ─┐ + │ │ ┌─────────────┐ │ + │ │ │ │ │ + │ │ │ │ │ + │ Pure │ ──────► ├────► │ + │ │ │ │ │ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ └─ │ + │ │ + │ │ + │ │ Nondestructive + │ │ + │ │ + │ ┌─ ┌─ │ + │ │ │ │ ┌─────────────┐ │ + │ │ │ └───► │ │ + │ │ │ │ │ │ + │ │ Query │ ──────► ├────► │ + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ └─────────────┘ │ + │ │ └─ ─┘ + │ │ + │ │ + │ Effectful │ + │ │ + │ │ + │ │ ┌─ ▲ ─┐ + │ │ │ ┌─────────────┐ │ │ + │ │ │ │ ├──┘ │ + │ │ │ │ │ │ + │ │ Command │ ──────► ├────► │ Destructive + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ └─────────────┘ │ + ▼ └─ └─ ─┘ +Unsafe +``` + +FIXME safety level MUST be defined by the pair `(URI Scheme, Ability)` (service metadata). This may need a field on the workflow. + +## 2.1 Pure Functions + +Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. + +Note that in IPVM, pre-resolved CID handles are treated as referentially transparent. See [CID Handles](FIXME). + +## 2.2 Nondestructive Effects + +For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. + +Nondestructive effects can be retried or raced safely. Each nondestructive invocation is unique, and need to be attested from a trusted source. Once their value enters the IPVM system, it is treated as a pure value. + +## 2.3 Destructive Effects + +Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. + +# 3 Content Handles + +FIXME This probably belongs in its own spec. Now that we have the basic concept, it keeps coming up in conversation. + +[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. However, this does not guarantee that the CID is resolvable at a particular time or place. + +A Content Handle (CHa) is a type that MUST only be created by the runtime and MUST NOT have a serializated representation. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. + +This that the CID has been checked, and the runner guarintees that it is available in the current environment. + +NOTE TO SELF: should CHa be its own spec? Seems useful :thinking: + +| Issue | At Fault | +|-------------------------------|------------------------| +| Malformed CID | Requestor | +| Cannot provide all CHa blocks | Runner | +| Cannot resolve CID to CHa | Network or Environment | + +``` js +// Just a sketch for now, don't judge me! +{ + "type": "ipvm/effect", + "version": "0.1.0", + "using": "cid:Qm12345", + "do": "handle/resolve" +} +``` + +```haskell +-- No, this won't survive into the fnal draft. Just stashing it here for now as a note! +resolve :: CID -> IO (Either Timeout CHa) +``` + +# 3 Pure Wasm + +Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly available via WASI or similar aside from the ability to read content addressed data. + +Note that while the function itself is pure, as is dereferencing content-addressed data, the function MAY fail if the CID is not available to the runner. + +The Wasm configuration MUST extend the core task type as follows: + +| Field | Type | Description | Required | Default | +|--------|-----------------------|-------------------------------------------|----------|---------| +| `v` | SemVer | The Wasm module's Wasm version | No | `0.1.0` | +| `func` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | +| `args` | `[{String : Any}]` | Arguments to the Wasm executable | Yes | | + +## 3.2 IPLDS Schema + +``` ipldsch +type WasmTask struct { + v SemVer + func String -- Function name to invoke + args [Any] -- Positional arguments FIXME **for now** -- we need to figure out WIT, I know +} +``` diff --git a/task/README.md b/task/README.md deleted file mode 100644 index 5dfa9fe..0000000 --- a/task/README.md +++ /dev/null @@ -1,342 +0,0 @@ -# IPVM Task Specification v0.1.0 - -> With hands of iron, there's not a task we couldn't do -> -> — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor - -## Editors - -* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) - -## Authors - -* [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) -* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) - -## Language - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). - -# 0 Abstract - -Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. - -# 1 Introduction - -Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. - -IPVM Tasks are defined as a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Tasks require certain fields in the `inputs` field to configure IPVM for timeouts, gas usage, credits, transactional guarantees, result visibility, and so on. - -## 2 Effects - -Tasks can be broken into categories: - -1. Pure -2. Effectful - * Destructive - * Nondestructive - -Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from [heat](https://en.wikipedia.org/wiki/Second_law_of_thermodynamics)). - -Effects interact with the world in some way . Reading from a database, sending an email, and firing missiles are all kinds of effect. - -One way to represent the difference between these pictorally is with box-and-wire diagrams. Here computation is drawn as a box. Explicit input and output are drawn as horizontal arrows, with input on the left and output on the right. Effects are drawn as vertical-pointing arrows. - -``` - Safe - ▲ ┌─ ─┐ - │ │ ┌─────────────┐ │ - │ │ │ │ │ - │ │ │ │ │ - │ Pure │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ └─ │ - │ │ - │ │ - │ │ Nondestructive - │ │ - │ │ - │ ┌─ ┌─ │ - │ │ │ │ ┌─────────────┐ │ - │ │ │ └───► │ │ - │ │ │ │ │ │ - │ │ Query │ ──────► ├────► │ - │ │ │ │ │ │ - │ │ │ │ │ │ - │ │ │ └─────────────┘ │ - │ │ └─ ─┘ - │ │ - │ │ - │ Effectful │ - │ │ - │ │ - │ │ ┌─ ▲ ─┐ - │ │ │ ┌─────────────┐ │ │ - │ │ │ │ ├──┘ │ - │ │ │ │ │ │ - │ │ Command │ ──────► ├────► │ Destructive - │ │ │ │ │ │ - │ │ │ │ │ │ - │ │ │ └─────────────┘ │ - ▼ └─ └─ ─┘ -Unsafe -``` - -FIXME safety level MUST be defined by the pair `(URI Scheme, Ability)` (service metadata). This may need a field on the workflow. - -## 2.1 Pure Functions - -Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. - -Note that in IPVM, pre-resolved CID handles are treated as referentially transparent. See [CID Handles](FIXME). - -## 2.2 Nondestructive Effects - -For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. - -Nondestructive effects can be retried or raced safely. Each nondestructive invocation is unique, and need to be attested from a trusted source. Once their value enters the IPVM system, it is treated as a pure value. - -## 2.3 Destructive Effects - -Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. - -# 3 Content Handles - -FIXME This probably belongs in its own spec. Now that we have the basic concept, it keeps coming up in conversation. - -[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. However, this does not guarantee that the CID is resolvable at a particular time or place. - -A Content Handle (CHa) is a type that MUST only be created by the runtime and MUST NOT have a serializated representation. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. - -This that the CID has been checked, and the runner guarintees that it is available in the current environment. - -NOTE TO SELF: should CHa be its own spec? Seems useful :thinking: - -| Issue | At Fault | -|-------------------------------|------------------------| -| Malformed CID | Requestor | -| Cannot provide all CHa blocks | Runner | -| Cannot resolve CID to CHa | Network or Environment | - -``` js -// Just a sketch for now, don't judge me! -{ - "type": "ipvm/effect", - "version": "0.1.0", - "using": "cid:Qm12345", - "do": "handle/resolve" -} -``` - -```haskell --- No, this won't survive into the fnal draft. Just stashing it here for now as a note! -resolve :: CID -> IO (Either Timeout CHa) -``` - -# 2 Envelope - -An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. - -``` ipldsch -type Action struct { - -- UCAN Invoctaion - using URI - do Ability - input Any - - -- IPVM Configuration - config TaskConfig (implicit {}) -} -``` - -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. - -| Field | Type | Description | Required | Default | -|----------|-------------------|-----------------------------------------|----------|-----------------| -| `v` | SemVer | IPVM Task Version | Yes | | -| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification` | How to verify the output | No | `"attestation"` | -| `time` | Integer | Timeout in milliseconds | No | `5000` | -| `memory` | Integer | Memory limit in KB | No | 1000 | -| `disk` | Integer | Disk limit in KB | No | 100000 | - -## 2.1 Fields - -### 2.1.1 Version - -The version of the IPVM - -### 2.1.2 Secret Flag - -The `secret` flag marks a task as being unsuitable for publication. - -If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. - -Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. - -### 2.1.3 Verification Method - -FIXME - -## 2.2 IPLD Schema - -``` ipldsch -type TaskConfig struct { - v SemVer - secret Boolean (implicit False) - check Verification (implicit Attestation) -} - -type Verification enum { - | Oracle - | Consensus(Integer) - | Optimistic(Integer) - | ZKP -} - -type Oracle enum { - | Attestation - | ThirdParty(DID) -} - -type Optimistic struct { - confirmations Integer - referee Referee -} - -type Referee enum { - | ZK(ZeroKnowledge) - | Trusted(URI) -} - -type Consensus struct { - agents [DID] - -- FIXME needs more detail on method etc -} - -type ZKP enum { - | Groth16 - | Nova - | Nova2 -``` - -## 2.3 JSON Examples - -``` json -{ - "using": "dns://example.com?TYPE=TXT", - "do": "crud/update", - "inputs": { - "value": "hello world" - }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "timeout": { "ms": 5000 }, - "retries": 5, - "verification": "attestation" - } -} -``` - -# 3 Pure Wasm - -Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly available via WASI or similar aside from the ability to read content addressed data. - -Note that while the function itself is pure, as is dereferencing content-addressed data, the function MAY fail if the CID is not available to the runner. - -The Wasm configuration MUST extend the core task type as follows: - -| Field | Type | Description | Required | Default | -|--------|-----------------------|-------------------------------------------|----------|---------| -| `v` | SemVer | The Wasm module's Wasm version | No | `0.1.0` | -| `func` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | -| `args` | `[{String : Any}]` | Arguments to the Wasm executable | Yes | | - -## 3.2 IPLDS Schema - -``` ipldsch -type WasmTask struct { - v SemVer - func String -- Function name to invoke - args [Any] -- Positional arguments FIXME **for now** -- we need to figure out WIT, I know -} -``` - -## 3.3 JSON Examples - -Deterministic WebAssembly - -``` js -{ - // Clean, but possible a bridge too far. Probably handle this in an implcit like CIDs - "supply-gas": { - "using": "gas:reserve://mine", // Or something... needs work at least - "do": "gas/supply", - "inputs": { - "on": ["/", "some-wasm"], - "max": 1000 - } - }, - "some-wasm": { - "using": "wasm:1:Qm12345", // Or something... wasm:Qm12345? - "do": "ipvm/run", - "inputs": { - "func": "calculate", - "args": [ - 1, - "hello world", - {"c": {"ucan/promise": ["/", "some-other-action"]}}, - {"a": 1, "b": 2, "c": 3} - ] - }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "check": {"optimistic": 2} - } - } -} -``` - -Docker - -``` json -{ - "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? - "do": "docker/run", - "inputs": { - "func": "calculate", - "args": [ - 1, - "hello world", - {"c": {"ucan/promise": ["/", "some-other-action"]}}, - {"a": 1, "b": 2, "c": 3} - ], - "container": { - "entry": "/", - "workdir": "/", - }, - "env": { - "$FOO": "bar" - } - }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "check": {"optimistic": 2} - } -} -``` - -# 5 Prior Art - -* [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) -* [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) -* [UCAN Invocation](https://github.com/ucan-wg/invocation) -* [WarpForge Formula](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) - -# 6 Acknowledgments - diff --git a/workflow/README.md b/workflow/README.md index 27e705e..31df45c 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -75,45 +75,17 @@ Shared-nothing architecture. Even if shared memory is used, it MUST be controlle The outer wrapper of a workflow MUST contain the following fields: -| Field | Type | Description | Required | Default | -|----------------|-----------------------------|-------------------------------------------|----------|---------| -| `type` | `"ipvm/workflow"` | Object type identifier | Yes | | -| `version` | `"0.1.0"` | IPVM workflow version | Yes | | -| `requestor` | DID | Requestor's DID | Yes | | -| `nonce` | String | Unique nonce | Yes | | -| `verification` | `{"optimistic": 2} or null` | | Yes | | -| `meta` | `&Object` | User-defined object (tags, comments, etc) | No | `{}` | -| `parent` | `CID-relative Path or null` | The CID of the initiating task (if any) | No | `null` | -| `defaults` | `IpvmConfig` | | No | `{}` | -| `tasks` | `{String => Task}` | Named tasks | Yes | | -| `exception` | `Task.Wasm` | | No | `null` | -| `signature` | Varsig | Signature of serialized fields | Yes | | - -``` ipldsch -FIXME - -type Workflow struct { - ver String - req DID - nnc String - vfy Verification - meta &{String:String} implicit {} - par &Task optional nullable - dfl Config implicit {} - tsks {String: Task} - exc &Wasm optional nullable - sig -} - -type Verification union { - | OptimisticVerification - | "snark" -} representation keyed - -type OptimisticVerification struct { - optimistic Integer -} -``` +| Field | Type | Description | Required | Default | +|-------------|----------------------------------------------------|--------------------------------------------------|----------|---------| +| `type` | `"ipvm/workflow"` | Object type identifier | Yes | | +| `v` | `"0.1.0"` | IPVM workflow version | Yes | | +| `nnc` | String | Unique nonce | Yes | | +| `meta` | `Object` | User-defined object (tags, comments, etc) | No | `{}` | +| `par` | `CID-relative Path or null` | The CID of the initiating (parent) task (if any) | No | `null` | +| `defaults` | `IpvmConfig` | | No | `{}` | +| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | +| `exception` | `Task.DeterminisicWasm` | | No | `null` | +| `signature` | [Varsig](https://github.com/ChainAgnostic/varsig/) | Varsig (IPLD signature) of serialized fields | Yes | | ## 2.1 Fields @@ -159,80 +131,118 @@ See the [Task](FIXME) section for more. The `exception` field contains a Task with predefined inputs. See the [Exception Handling](FIXME) section for more. +Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. + +It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). + +Each task MAY include a failure workflow to run on failure. + +Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. + + ## 2.1.10 Signature The signature of the CID represented by the other fields. -## 2.2 Example - -Here is a simple example: +## 2.2 IPLD Schema -``` -┌─────────┐ ┌─────────┐ -│ │ │ │ -│ left │ │ right │ -│ │ │ │ -└────────┬┘ └─┬───────┘ - │ │ - │ │ - ┌─▼──────▼┐ - │ │ - │ end │ - │ │ - └─────────┘ +``` ipldsch +type Workflow struct { + v SemVer + nnc String + meta {String : Any} (implicit {}) + parent &Task (implicit Null) + defauts SystemConfig (implicit {}) + tasks UCAN.Invocation + exception &Task.DeterministicWasm (implicit Null) + sig Bytes +} ``` -Here, two tasks (`left` and `right`) are used as input to a third task (`end`). This is fully configured in IPVM as: +## 2.3 JSON Exmaples ``` json { "type": "ipvm/workflow", - "version": "0.1.0", - "requestor": "did:key:zAlice", - "nonce": "o3--8Gdu5", - "verification": {"optimistic": 2}, - "tasks": { - "left": { - "type": "ipvm/wasm", - "version": "0.1.0", - "wasm": "bafkreiecadaahndb55cgvemhctwoojcc4hv4alogybpqndzj4mq7brixcy", - "inputs": [], - "outputs": ["foo", "bar"] - }, - "right": { - "type": "ipvm/wasm", - "wasm": "bafkreidrvex7kbqiow7gbqzvj452hr3vbifmfvyd55qicfwrw6xvq3qnlq", - "inputs": [ - { "quux": "bafy123" } - ] - }, - "end": { - "type": "ipvm/wasm", - "wasm": "bafkreihvr3nup2lpny3ip3hkqv7s7ggq5wit5dkvyaexztnl54rkrlbdhe", - "inputs": [ - { "a": { "from": "left", "output": "bar" } }, - { "b": { "from": "right" } }, - { "c": 123 } - ] - } - }, + "v": "0.1.0", + // MORE HERE "signature": "abcdef" } ``` -# 3 Global Defaults +# 3 System Configuation The global defaults object contains options for the Workflow itself, as well as cascading defaults for Tasks. -| Field | Type | Description | Required | Default | -|-----------|--------------|------------------------------|----------|---------| -| | | | | | -| `wasm` | `CID or URI` | Reference to the Wasm to run | Yes | | -| `effects` | | | | | - -## 3.1 Global Wasm Configuration +| Field | Type | Description | Required | Default | +|----------|-------------------|-----------------------------------------|----------|-----------------| +| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | +| `check` | `Verification` | How to verify the output | No | `"attestation"` | +| `time` | Integer | Timeout in milliseconds | No | `5000` | +| `memory` | Integer | Memory limit in KB | No | `1000` | +| `disk` | Integer | Disk limit in KB | No | `100000` | + +## 3.1 Fields + +### 3.1.1 Version + +The version of the IPVM + +### 3.1.2 Secret Flag + +The `secret` flag marks a task as being unsuitable for publication. -## 3.2 Global Effects Configuration +If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. + +Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. + + + +FIXME + +## 3.2 IPLD Schema + +``` ipldsch +type SystemConfig struct { + secret Boolean (implicit False) + check Verification (implicit Attestation) + time Integer + memory Integer + disk Integer +} + +type Verification enum { + | Oracle + | Consensus(Integer) + | Optimistic(Integer) + | ZKP +} + +type Oracle enum { + | Attestation + | ThirdParty(DID) +} + +type Optimistic struct { + confirmations Integer + referee Referee +} + +type Referee enum { + | ZK(ZeroKnowledge) + | Trusted(URI) +} + +type Consensus struct { + agents [DID] + -- FIXME needs more detail on method etc +} + +type ZKP enum { + | Groth16 + | Nova + | Nova2 +``` # 4 Tasks @@ -240,62 +250,127 @@ While an indivdual invocation is structured like an AST (and eventually memoized For more detail, refer to the [Task](FIXME) spec -## 4.1 Pipelining +Task Canonicalization -- FIXME ref the UCAN Invocation spec -The output of one task is often the input to another. This is called pipelining, and MUST form one or more directed acyclic graphs (DAGs). Graphs MAY be unrooted. -For example, this is a legal set of task graphs in a single workflow. -``` -┌───────────────Workflow Tasks────────────────┐ -│ │ -│ │ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ -│ └───────┬─┘ └─┬───────┘ └────┬────┘ │ -│ │ │ │ │ -│ │ │ │ │ -│ ┌─▼──────▼─┐ ┌────▼────┐ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ └─┬──────┬─┘ └─────────┘ │ -│ │ │ │ -│ │ │ │ -│ ┌───────▼─┐ ┌─▼───────┐ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ └─────────┘ └─────────┘ │ -│ │ -│ │ -└─────────────────────────────────────────────┘ -``` +# Task + +> With hands of iron, there's not a task we couldn't do +> +> — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor -Pipelining is acheived via dataflow. Every task is given a name, and its output(s) MUST be referenced by either the task's CID or its name inside the task map. If the task has more than one output, the output MUST be referenced by index (starting from 0) or name. +Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. -``` -{"from": "previousTask"} -{"from": "previousTask", "out": 3} -{"from": "bafkreifovuswf6ss7czm5gk6ibnd7klhoojhynmiydj6cf7p2yjdsevlga", "out": "firstName" } +Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. + +# 1.1 Task Envelope + +IPVM Tasks are defined as an extension of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. + +An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. + +``` ipldsch +type Action struct { + using URI + do Ability + input Any + taskConfig TaskConfig (implicit {}) +} ``` -References by CID MAY be tasks that executed outside of the current workflow. If the task was not yet executed, it MUST NOT run inside this Workflow. +All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. -# 7 Exception Handler -Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. +## 2.3 JSON Examples -It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). +``` json +{ + "using": "dns://example.com?TYPE=TXT", + "do": "crud/update", + "inputs": { + "value": "hello world" + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "timeout": { "ms": 5000 }, + "retries": 5, + "verification": "attestation" + } +} +``` -Each task MAY include a failure workflow to run on failure. +Deterministic WebAssembly -Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. +``` js +{ + // Clean, but possible a bridge too far. Probably handle this in an implcit like CIDs + "supply-gas": { + "using": "gas:reserve://mine", // Or something... needs work at least + "do": "gas/supply", + "inputs": { + "on": ["/", "some-wasm"], + "max": 1000 + } + }, + "some-wasm": { + "using": "wasm:1:Qm12345", // Or something... wasm:Qm12345? + "do": "ipvm/run", + "inputs": { + "func": "calculate", + "args": [ + 1, + "hello world", + {"c": {"ucan/promise": ["/", "some-other-action"]}}, + {"a": 1, "b": 2, "c": 3} + ] + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": {"optimistic": 2} + } + } +} +``` + +Docker + +``` json +{ + "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? + "do": "docker/run", + "inputs": { + "func": "calculate", + "args": [ + 1, + "hello world", + {"c": {"ucan/promise": ["/", "some-other-action"]}}, + {"a": 1, "b": 2, "c": 3} + ], + "container": { + "entry": "/", + "workdir": "/", + }, + "env": { + "$FOO": "bar" + } + }, + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": {"optimistic": 2} + } +} +``` + +# 5 Related Work and Prior Art -# 8 Related Work and Prior Art +* [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) +* [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) +* [WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) AWS Lambda workflow specs OCI @@ -310,7 +385,7 @@ AquaVM It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), -# 9 Acknowledgments +# 6 Acknowledgments * Steb * Mel From 14e23e286ab774fab813358e0b3ef82b7d18e337 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 28 Nov 2022 10:38:48 -0800 Subject: [PATCH 28/42] Remove the "provisionally" --- workflow/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/README.md b/workflow/README.md index 31df45c..8cd44d6 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -11,7 +11,7 @@ ## Authors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) -* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) _(TODO: Provisionally!)_ +* [Simon Worthington](https://github.com/simonwo), [Bacalhau Project](https://www.bacalhau.org/) ## Language From 2922260578923956782a0272e4780ad5a83cfedc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 17:46:17 -0800 Subject: [PATCH 29/42] Units, early cleanup --- workflow/README.md | 245 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 195 insertions(+), 50 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 8cd44d6..15b721f 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -17,15 +17,21 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). +## Dependencies + +* [DAG-CBOR](https://ipld.io/specs/codecs/dag-cbor/spec/) +* [UCAN Invocation](https://github.com/ucan-wg/invocation/) +* [VarSig](https://github.com/ChainAgnostic/varsig/) + # 0 Abstract -An IPVM Workflow defines the global configuration for a proposed workflow, their individual tasks, dependencies between tasks, any required authorization, authentication, and so on. An IPVM Workflows is an envelope for both the configuration and content layers common to declarative workflow specifications. +An IPVM Workflow defines everything required to execute one or more tasks: global configuration for a proposed workflow, their individual tasks, dependencies between tasks, authorization, metadata, signatures, and so on. # 1 Introduction The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. -While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a workflow, they assume an established audience (which too ridig for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. +While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a workflow, they assume an established audience (which too rigid for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and ___. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. @@ -37,13 +43,13 @@ IPVM Workflows MUST be suitable for the proposal of workflows and negotiation wi While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Workflows are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. -These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows using the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). +These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows with the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). The core restrictions enforced by the design of IPVM Workflows are: 1. Execution MUST terminate in finite time 2. Workflow tasks MUST form a partial order -3. Effects MUST be managed by the runtime and declared ahead of time +3. Effects MUST be decalared ahead of time and managed by the IPVM host While effects MUST be declared up front, they MAY also be emitted as output from pure computation (see the core spec for more). This provides a "legal" escape hatch for building higher-level abstraction that incorporate effects. @@ -75,17 +81,72 @@ Shared-nothing architecture. Even if shared memory is used, it MUST be controlle The outer wrapper of a workflow MUST contain the following fields: -| Field | Type | Description | Required | Default | -|-------------|----------------------------------------------------|--------------------------------------------------|----------|---------| -| `type` | `"ipvm/workflow"` | Object type identifier | Yes | | -| `v` | `"0.1.0"` | IPVM workflow version | Yes | | -| `nnc` | String | Unique nonce | Yes | | -| `meta` | `Object` | User-defined object (tags, comments, etc) | No | `{}` | -| `par` | `CID-relative Path or null` | The CID of the initiating (parent) task (if any) | No | `null` | -| `defaults` | `IpvmConfig` | | No | `{}` | -| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | -| `exception` | `Task.DeterminisicWasm` | | No | `null` | -| `signature` | [Varsig](https://github.com/ChainAgnostic/varsig/) | Varsig (IPLD signature) of serialized fields | Yes | | +| Field | Type | Description | Required | Default | +|-----------------|----------------------------------------------------|----------------------------------------------|----------|---------| +| `ipvm/workflow` | `Workflow` | IPVM Workflow | Yes | | +| `signature` | [Varsig](https://github.com/ChainAgnostic/varsig/) | Varsig (IPLD signature) of serialized fields | Yes | | + +| Field | Type | Description | Required | Default | +|-------------|-----------------------------|--------------------------------------------------|----------|---------| +| `v` | `"0.1.0"` | IPVM workflow version | Yes | | +| `nnc` | `String` | Unique nonce | Yes | | +| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | +| `par` | `CID-relative Path or null` | The CID of the initiating (parent) task (if any) | No | `null` | +| `defaults` | `IpvmConfig` | | No | `{}` | +| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | +| `exception` | `Task.DeterminisicWasm` | | No | `null` | + +``` ipldsch +type SignedWorkflow { + inv UCAN.Invocation + +} +``` + +``` json +{ + "ucan/invoke": { + "v": "0.1.0", + "nnc": "02468", + "ext": { + "ipvm/config": { + + } + }, + "prf": [ + {"/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy"}, + {"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"} + ], + "run": { + "notify-bob": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": [ + { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": "Hello Bob!" + } + ], + "ipvm/config": { + "time": {"seconds": "100"} + } + }, + "log-as-done": { + "with": "https://example.com/report" + "do": "crud/update" + "inputs": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com"], + "event": "email-notification", + "value": {"ucan/promise": ["/", "notify-bob"]} // Pipelined promise + } + } + } + }, + "sig": {"/": {"bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7"}} +} +``` ## 2.1 Fields @@ -147,15 +208,18 @@ The signature of the CID represented by the other fields. ## 2.2 IPLD Schema ``` ipldsch +type SignedWorkflow struct { + wfl Workflow + sig VarSig +} + type Workflow struct { v SemVer - nnc String meta {String : Any} (implicit {}) - parent &Task (implicit Null) + parent nullable &Task (implicit Null) defauts SystemConfig (implicit {}) tasks UCAN.Invocation exception &Task.DeterministicWasm (implicit Null) - sig Bytes } ``` @@ -163,8 +227,11 @@ type Workflow struct { ``` json { - "type": "ipvm/workflow", - "v": "0.1.0", + "ipvm/workflow": { + "v": "0.1.0", + "nnc": "9dn-3*", + + } // MORE HERE "signature": "abcdef" } @@ -174,13 +241,13 @@ type Workflow struct { The global defaults object contains options for the Workflow itself, as well as cascading defaults for Tasks. -| Field | Type | Description | Required | Default | -|----------|-------------------|-----------------------------------------|----------|-----------------| -| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification` | How to verify the output | No | `"attestation"` | -| `time` | Integer | Timeout in milliseconds | No | `5000` | -| `memory` | Integer | Memory limit in KB | No | `1000` | -| `disk` | Integer | Disk limit in KB | No | `100000` | +| Field | Type | Description | Required | Default | +|----------|-------------------|-----------------------------------------|----------|--------------------------| +| `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | +| `check` | `Verification` | How to verify the output | No | `"attestation"` | +| `time` | `TimeLength` | Timeout | No | `[5, "minutes"]` | +| `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | +| `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | ## 3.1 Fields @@ -194,7 +261,7 @@ The `secret` flag marks a task as being unsuitable for publication. If the `sceret` field is explicitely set, the task MUST be treated per that setting. If not set, the `secret` field defaults to `null`, which behaves as a soft `false`. If such a task consumes input from a `secret` source, it is also marked as `secret`. -Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider using [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. +Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider with [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. @@ -204,22 +271,22 @@ FIXME ``` ipldsch type SystemConfig struct { - secret Boolean (implicit False) + secret Boolean (implicit False) check Verification (implicit Attestation) time Integer memory Integer disk Integer } -type Verification enum { +type Verification union { | Oracle - | Consensus(Integer) - | Optimistic(Integer) + | Consensus + | Optimistic | ZKP -} +} representation keyed -type Oracle enum { - | Attestation +type Oracle union { + | Attestation "attestation" | ThirdParty(DID) } @@ -230,18 +297,18 @@ type Optimistic struct { type Referee enum { | ZK(ZeroKnowledge) - | Trusted(URI) + | Trusted(DID) } type Consensus struct { agents [DID] - -- FIXME needs more detail on method etc } type ZKP enum { | Groth16 | Nova | Nova2 +} ``` # 4 Tasks @@ -254,7 +321,7 @@ Task Canonicalization -- FIXME ref the UCAN Invocation spec -# Task +# Tasks > With hands of iron, there's not a task we couldn't do > @@ -264,21 +331,30 @@ Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. + + +TODO + - deal version: prf: [] + - actionable version prf: [ucans] + # 1.1 Task Envelope IPVM Tasks are defined as an extension of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. + ``` ipldsch type Action struct { - using URI + with URI do Ability input Any taskConfig TaskConfig (implicit {}) } ``` +A Task is a subtype of a UCAN Action + All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. @@ -287,17 +363,19 @@ All tasks MUST contain at least the following fields. They MAY contain others, d ``` json { - "using": "dns://example.com?TYPE=TXT", + "with": "dns://example.com?TYPE=TXT", "do": "crud/update", "inputs": { "value": "hello world" }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "timeout": { "ms": 5000 }, - "retries": 5, - "verification": "attestation" + "meta": { + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "timeout": { "ms": 5000 }, + "retries": 5, + "verification": "attestation" + } } } ``` @@ -308,7 +386,7 @@ Deterministic WebAssembly { // Clean, but possible a bridge too far. Probably handle this in an implcit like CIDs "supply-gas": { - "using": "gas:reserve://mine", // Or something... needs work at least + "with": "gas:reserve://mine", // Or something... needs work at least "do": "gas/supply", "inputs": { "on": ["/", "some-wasm"], @@ -316,7 +394,7 @@ Deterministic WebAssembly } }, "some-wasm": { - "using": "wasm:1:Qm12345", // Or something... wasm:Qm12345? + "with": "wasm:1:Qm12345", // Or something... wasm:Qm12345? "do": "ipvm/run", "inputs": { "func": "calculate", @@ -340,7 +418,7 @@ Docker ``` json { - "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? + "with": "docker:1:Qm12345", // Or something... wasm:Qm12345? "do": "docker/run", "inputs": { "func": "calculate", @@ -366,13 +444,80 @@ Docker } ``` +# 5 Appendix + +## 5.1 Support Types + +``` ipldsch +type TimeUnit enum { + | Seconds + | Minutes + | Days + | Weeks + | Years +} + +type InfoUnit enum { + | Bits + | Nibble + | Bytes + | Word32 + | Word64 +} + +type Unit union { + | TimeUnit + | InfoUnit +} + +type SIPrefix enum { + | Pico "p" + | Nano "n" + | Micro "u" + | Milli "m" + | Centi "c" + | Deci "d" + | Unity + | Deca "da" + | Hecto "ha" + | Kilo "k" + | Mega "M" + | Giga "G" + | Tera "T" + | Peta "P" + | Exa "E" +} + +type TimeLength struct { + magnitude integer + prefix Prefix (implicit Unity) + unit TimeUnit +} representation tuple + +type InfoSize struct { + magnitude integer + prefix Prefix (implicit Unity) + unit InfoUnit +} representation tuple + +type Measure union { + | TimeLength + | InfoSize +} + +[400, "nano", "seconds"] +``` + + + # 5 Related Work and Prior Art * [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) * [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) * [WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) -AWS Lambda workflow specs +AWS Lambda workflows +GH Workflows OCI E Language CapNet From b0aa0d34d03cb0940b14f8324b075e73e3c6800c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 19:32:26 -0800 Subject: [PATCH 30/42] Tightening up the types; nesting instead of subtyppin the config --- workflow/README.md | 249 +++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 121 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 15b721f..d793d1f 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -1,9 +1,5 @@ # IPVM Workflow Specification v0.1.0 -> In late 1970 or early ’71 I approached IBM Canada’s Intellectual Property department to see if we could take out a patent on the basic idea [of dataflow]. Their recommendation, which I feel was prescient, was that this concept seemed to them more like a law of nature, which is not patentable. -> -> J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) - ## Editors * [Brooklyn Zelenka](https://github.com/expede), [Fission](https://fission.codes) @@ -25,10 +21,14 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -An IPVM Workflow defines everything required to execute one or more tasks: global configuration for a proposed workflow, their individual tasks, dependencies between tasks, authorization, metadata, signatures, and so on. +An IPVM Workflow is a declarative cofiguration. A Workflow provides everything required to execute one or more tasks: defaults, tasks and their dependencies, authorization, metadata, signatures, and so on. # 1 Introduction +> In late 1970 or early ’71 I approached IBM Canada’s Intellectual Property department to see if we could take out a patent on the basic idea [of dataflow]. Their recommendation, which I feel was prescient, was that this concept seemed to them more like a law of nature, which is not patentable. +> +> J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) + The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a workflow, they assume an established audience (which too rigid for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. @@ -81,72 +81,19 @@ Shared-nothing architecture. Even if shared memory is used, it MUST be controlle The outer wrapper of a workflow MUST contain the following fields: -| Field | Type | Description | Required | Default | -|-----------------|----------------------------------------------------|----------------------------------------------|----------|---------| -| `ipvm/workflow` | `Workflow` | IPVM Workflow | Yes | | -| `signature` | [Varsig](https://github.com/ChainAgnostic/varsig/) | Varsig (IPLD signature) of serialized fields | Yes | | - -| Field | Type | Description | Required | Default | -|-------------|-----------------------------|--------------------------------------------------|----------|---------| -| `v` | `"0.1.0"` | IPVM workflow version | Yes | | -| `nnc` | `String` | Unique nonce | Yes | | -| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | -| `par` | `CID-relative Path or null` | The CID of the initiating (parent) task (if any) | No | `null` | -| `defaults` | `IpvmConfig` | | No | `{}` | -| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | -| `exception` | `Task.DeterminisicWasm` | | No | `null` | - -``` ipldsch -type SignedWorkflow { - inv UCAN.Invocation - -} -``` +| Field | Type | Description | Required | +|-----------------|------------|-------------------------------------------------------------------------|----------| +| `ipvm/workflow` | `Workflow` | IPVM Workflow | Yes | +| `signature` | `VarSig` | [VarSig](https://github.com/ChainAgnostic/varsig/) of serialized fields | Yes | -``` json -{ - "ucan/invoke": { - "v": "0.1.0", - "nnc": "02468", - "ext": { - "ipvm/config": { - - } - }, - "prf": [ - {"/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy"}, - {"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"} - ], - "run": { - "notify-bob": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": [ - { - "to": "bob@example.com", - "subject": "DNSLink for example.com", - "body": "Hello Bob!" - } - ], - "ipvm/config": { - "time": {"seconds": "100"} - } - }, - "log-as-done": { - "with": "https://example.com/report" - "do": "crud/update" - "inputs": { - "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com"], - "event": "email-notification", - "value": {"ucan/promise": ["/", "notify-bob"]} // Pipelined promise - } - } - } - }, - "sig": {"/": {"bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7"}} -} -``` +| Field | Type | Description | Required | Default | +|-------------|-------------------------|-------------------------------------------------------------------------------------|----------|---------| +| `v` | `"0.1.0"` | IPVM workflow version | Yes | | +| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | +| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `null` | +| `defaults` | `Config` | | No | `{}` | +| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | +| `on` | `Listeners` | IPVM event listeners | No | `{}` | ## 2.1 Fields @@ -215,11 +162,15 @@ type SignedWorkflow struct { type Workflow struct { v SemVer - meta {String : Any} (implicit {}) - parent nullable &Task (implicit Null) - defauts SystemConfig (implicit {}) + meta {String : Any} (implicit {}) + parent nullable &Task (implicit Null) + defauts SystemConfig (implicit {}) tasks UCAN.Invocation - exception &Task.DeterministicWasm (implicit Null) + on Listeners (implicit {}) -- FIXME or just excpetion? +} + +type Listeners struct { + exception &Wasm nullable (implicit Null) } ``` @@ -229,11 +180,60 @@ type Workflow struct { { "ipvm/workflow": { "v": "0.1.0", - "nnc": "9dn-3*", - - } - // MORE HERE - "signature": "abcdef" + "meta": { + "parent": null, + } + "defaults": { + "global": { + "time": [10, "minutes"], + }, + "task": { + "gas": 1000, + "memory": [10, "mega", "bytes"] + } + }, + "on": { + "exception": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe" + }, + "tasks": "ucan/invoke": { + "v": "0.1.0", + "nnc": "02468", + "prf": [ -- FIXME having to resend this is a pain! + {"/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy"}, + {"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"} + ], + "run": { + "notify-bob": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": [ + { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": "Hello Bob!" + } + ], + "meta": { + "ipvm/config": { + "time": {"seconds": "100"}, + "secret": true + } + } + }, + "log-as-done": { + "with": "https://example.com/report" + "do": "crud/update" + "inputs": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com"], + "event": "email-notification", + "value": {"ucan/promise": ["/", "notify-bob"]} // Pipelined promise + } + } + } + } + }, + "sig": {"/": {"bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7"}} } ``` @@ -248,12 +248,13 @@ The global defaults object contains options for the Workflow itself, as well as | `time` | `TimeLength` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | +| `gas` | `Integer` | Gas limit | No | `1000` | ## 3.1 Fields ### 3.1.1 Version -The version of the IPVM +The version of the IPVM Workflow ### 3.1.2 Secret Flag @@ -263,19 +264,40 @@ If the `sceret` field is explicitely set, the task MUST be treated per that sett Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider with [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. +### 3.1.3 Verification Strategy +The OPTIONAL `check` field MUST supply a verification strategy if present. If omitted, it MUST default to Attestation. FIXME +### 3.1.4 Time Quota + +The `time` field configures the upper limit in wall-clock time that the executor SHOULD allow. + +### 3.1.5 Memory Quota + +The `memory` field configures the upper limit in system memory that the executor SHOULD allow. + +### 3.1.6 Disk Quota + +The `disk` field configures the upper limit in system memory that the executor SHOULD allow. + +### 3.1.7 Gas Quota + +The `disk` field configures the upper limit in Wasm gas that the executor SHOULD allow. + +-- FIXME configuarble gas schedule? + ## 3.2 IPLD Schema ``` ipldsch type SystemConfig struct { secret Boolean (implicit False) check Verification (implicit Attestation) - time Integer + time Time memory Integer disk Integer + gas Integer } type Verification union { @@ -311,55 +333,38 @@ type ZKP enum { } ``` -# 4 Tasks - -While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a workflow spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. - -For more detail, refer to the [Task](FIXME) spec - -Task Canonicalization -- FIXME ref the UCAN Invocation spec - +## 3.3 JSON Examples +``` json +{ + "secret": true, + "check": {"optimistic": {"confirmations": 2, "referee": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4"}}, + "time": [45, "minutes"], + "memory": [500, "kilo", "bytes"], + "disk": [20, "mega", "bytes"], + "gas": 5000 +} +``` -# Tasks +# 4 Task Envelope > With hands of iron, there's not a task we couldn't do > > — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor +While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a workflow spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. + Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. - - -TODO - - deal version: prf: [] - - actionable version prf: [ucans] - -# 1.1 Task Envelope - IPVM Tasks are defined as an extension of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. +A Task is a UCAN Action. A `meta["ipvm/config"]` field MAY be used to configure IPVM for this task. -``` ipldsch -type Action struct { - with URI - do Ability - input Any - taskConfig TaskConfig (implicit {}) -} -``` - -A Task is a subtype of a UCAN Action - -All tasks MUST contain at least the following fields. They MAY contain others, depending on their type. - - - -## 2.3 JSON Examples +## 4.1 JSON Examples ``` json { @@ -370,9 +375,8 @@ All tasks MUST contain at least the following fields. They MAY contain others, d }, "meta": { "ipvm/config": { - "v": "0.1.0", "secret": false, - "timeout": { "ms": 5000 }, + "timeout": [500, "milli", "seconds"], "retries": 5, "verification": "attestation" } @@ -508,8 +512,6 @@ type Measure union { [400, "nano", "seconds"] ``` - - # 5 Related Work and Prior Art * [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) @@ -532,9 +534,14 @@ It is not possible to mention the separation of effects from computation without # 6 Acknowledgments -* Steb -* Mel -* Christine +* Steven Allen +* Melanie Riise +* Christine Lemmer-Webber +* Peter Alvaro +* Juan Benet +* Mark Miller * Blaine Cook * Luke Marsden * Quinn Wilton +* Zeeshan Lakhani +* Irakli Gozalishvili From 91230500c14a8ac41910413ac967dc15846ba78f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 20:33:58 -0800 Subject: [PATCH 31/42] Tightening up the wrapper format --- README.md | 68 +++++++++++++++ workflow/README.md | 202 ++++++++++++--------------------------------- 2 files changed, 122 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 83c3317..d6b6b66 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,41 @@ Partial failure in a deterministic system is simplified by using transactional s └───────────────────────────────────────────────────────────────────────────┘ ``` +## 1.2 Humane Design + +> People are part of the system. The design should match the user's experience, expectations, and mental models. +> +> — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design + +While higher-level interfaces over IPVM Workflows MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving workflows and tasks between machines, logging, and execution. IPVM Workflows aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM workflows follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. + +## 1.3 Security Considerations + +> A program can create a controlled environment within which another, possible untrustworthy program, can be run safely [but] may leak, i.e., transmit [...] the input data which the customer gives it [...] We will call the problem of constraining a service [from leaking sensitive data] the confinement problem. +> +> Butler W. Lampson, A Note on the Confinement Problem, Communications of the ACM + +IPVM runs in trustless ("mutually suspicious") environments. Conceivably either a workflow proposer or service provider could be mallicious. To limit ___. + +Working with encrypted data and application secrets (section X.Y) is common practice for many workflows. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Workflow. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and not distributed. + +While it is tempting to push authorization concerns to a serapate layer, this has historically lead systems to be built on fundamentally insecure primitives. As such, IPVM Workflows include security considerations directly. It is not possible to control the security model of external effects, but it is possible to secure the inbound boundary to IPVM. + +Pure computation is always allowed as long as it terminates in a fixed number of steps. An executor + +Shared-nothing architecture. Even if shared memory is used, it MUST be controlled externally via the effect system (i.e. an outside agent). + # 2 Effect System +The core restrictions enforced by the design of IPVM Workflows are: + +1. Execution MUST terminate in finite time +2. Workflow tasks MUST form a partial order +3. Effects MUST be decalared ahead of time and controlled by the IPVM host + +While effects MUST be declared up front, they MAY also be emitted as output from pure computation (see the core spec for more). This provides a "legal" escape hatch for building higher-level abstraction that incorporate effects. + + ## 2.1 Pure Functions ## 2.2 Nondestructive Effects @@ -216,6 +249,41 @@ At the lowest level, IPVM jobs only describe the loading of immutible data. * Vats * Map/reduce +``` ipldsch +type Verification union { + | Oracle + | Consensus + | Optimistic + | ZKP +} representation keyed + +type Oracle union { + | Attestation "attestation" + | ThirdParty(DID) +} + +type Optimistic struct { + confirmations Integer + referee Referee +} + +type Referee enum { + | ZK(ZeroKnowledge) + | Trusted(DID) +} + +type Consensus struct { + agents [DID] +} + +type ZKP enum { + | Groth16 + | Nova + | Nova2 +} +``` + + # 3 Acknowledgments * [Joe Armstrong](https://joearms.github.io/), Ericsson diff --git a/workflow/README.md b/workflow/README.md index d793d1f..d1f2adf 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -29,11 +29,9 @@ An IPVM Workflow is a declarative cofiguration. A Workflow provides everything r > > J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) -The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with live pipes. +The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with [promise pipelining](http://erights.org/elib/distrib/pipeline.html) (essentially distributed pipes). -While capability sytems such as [UCAN](https://github.com/ucan-wg/spec/) include the information required to execute a workflow, they assume an established audience (which too rigid for negotiation), do not signal the _intent_ to execute, and do not include fields to configure settings for the actual runtime. - -IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and ___. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. +IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and verification. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. ## 1.1 Design Philosophy @@ -45,38 +43,6 @@ While IPVM in aggregate is capable of executing arbitrary programs, individual I These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows with the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). -The core restrictions enforced by the design of IPVM Workflows are: - -1. Execution MUST terminate in finite time -2. Workflow tasks MUST form a partial order -3. Effects MUST be decalared ahead of time and managed by the IPVM host - -While effects MUST be declared up front, they MAY also be emitted as output from pure computation (see the core spec for more). This provides a "legal" escape hatch for building higher-level abstraction that incorporate effects. - -## 1.2 Humane Design - -> People are part of the system. The design should match the user's experience, expectations, and mental models. -> -> — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design - -While higher-level interfaces over IPVM Workflows MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving workflows and tasks between machines, logging, and execution. IPVM Workflows aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM workflows follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. - -## 1.3 Security Considerations - -> A program can create a controlled environment within which another, possible untrustworthy program, can be run safely [but] may leak, i.e., transmit [...] the input data which the customer gives it [...] We will call the problem of constraining a service [from leaking sensitive data] the confinement problem. -> -> Butler W. Lampson, A Note on the Confinement Problem, Communications of the ACM - -IPVM runs in trustless ("mutually suspicious") environments. Conceivably either a workflow proposer or service provider could be mallicious. To limit ___. - -Working with encrypted data and application secrets (section X.Y) is common practice for many workflows. IPVM treats these as effects and affinities. As it is intended to operate on a public network, secrets MUST NOT be hardcoded into an IPVM Workflow. Any task that involves a dereferenced secret or decrypted data — including its downstream consumers — MUST be marked as secret and not distributed. - -While it is tempting to push authorization concerns to a serapate layer, this has historically lead systems to be built on fundamentally insecure primitives. As such, IPVM Workflows include security considerations directly. It is not possible to control the security model of external effects, but it is possible to secure the inbound boundary to IPVM. - -Pure computation is always allowed as long as it terminates in a fixed number of steps. An executor - -Shared-nothing architecture. Even if shared memory is used, it MUST be controlled externally via the effect system (i.e. an outside agent). - # 2 Envelope The outer wrapper of a workflow MUST contain the following fields: @@ -86,72 +52,48 @@ The outer wrapper of a workflow MUST contain the following fields: | `ipvm/workflow` | `Workflow` | IPVM Workflow | Yes | | `signature` | `VarSig` | [VarSig](https://github.com/ChainAgnostic/varsig/) of serialized fields | Yes | -| Field | Type | Description | Required | Default | -|-------------|-------------------------|-------------------------------------------------------------------------------------|----------|---------| -| `v` | `"0.1.0"` | IPVM workflow version | Yes | | -| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | -| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `null` | -| `defaults` | `Config` | | No | `{}` | -| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | -| `on` | `Listeners` | IPVM event listeners | No | `{}` | +| Field | Type | Description | Required | Default | +|------------|---------------------|-------------------------------------------------------------------------------------|----------|---------| +| `v` | `"0.1.0"` | IPVM workflow version | Yes | | +| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | +| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `null` | +| `config` | `Config` | | No | `{}` | +| `defaults` | `Config` | | No | `{}` | +| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | +| `on` | `Listeners` | IPVM event listeners | No | `{}` | ## 2.1 Fields -## 2.1.1 Type +## 2.1.1 Version -The `type` field MUST be set to `ipvm/workflow`. This field together with the `version` field indicates the expected fields and minimal semantics for the workflow. +The `v` field MUST contain the IPVM Workflow version. -## 2.1.2 Version +## 2.1.2 Metadata -The `version` field MUST be set to `0.1.0`. This field together with the `type` field indicates the expected fields and minimal semantics for the workflow. +The OPTIONAL `meta` field contains a user-definable JSON object. This is useful for including things like tags, comments, and so on. -## 2.1.3 Requestor - -The `requestor` field MUST be set to the DID of the agent requesting the workflow. The Rquestor is the only identified agent in a Workflow. - -The `signature` field MUST validate with a public key associated with the Requestor's DID. - -## 2.1.4 Nonce - -The `nonce` field MUST be a one-time-use random string. At least 12 random bytes encoded as base64 is RECOMMENDED. - -## 2.1.5 Parent +## 2.1.3 Parent The OPTIONAL `parent` field contains the CID of the IPVM Task that initiated it (if any). -## 2.1.6 Meta - -The `meta` field contains a user-definable JSON object. This is useful for including things like tags, comments, and so on. +## 2.1.4 Config - +The OPTIONAL global `config` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. -## 2.1.7 Defaults +## 2.1.4 Defaults -The global `defaults` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. +The OPTIONAL global `defaults` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. -## 2.1.8 Run +## 2.1.8 Tasks -The `run` field contains all of the IPVM Tasks set to run in this Workflow, each labelled by a human-readable key. +The `tasks` field contains all of the IPVM [Tasks](FIXME) set to run in this Workflow, each labelled by a human-readable key. -See the [Task](FIXME) section for more. +## 2.1.9 Listeners -## 2.1.9 Exception +The OPTIONAL `on` map MAY contain an `exception` field. The `exception` field contains a Task with predefined inputs. See the [Exception Handling](FIXME) section for more. -Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. - -It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). - -Each task MAY include a failure workflow to run on failure. - -Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. - - -## 2.1.10 Signature - -The signature of the CID represented by the other fields. - ## 2.2 IPLD Schema ``` ipldsch @@ -237,14 +179,14 @@ type Listeners struct { } ``` -# 3 System Configuation +# 3 Configuation -The global defaults object contains options for the Workflow itself, as well as cascading defaults for Tasks. +The IPVM configuration struct sets secrecy, quotas, and verification strategy. | Field | Type | Description | Required | Default | |----------|-------------------|-----------------------------------------|----------|--------------------------| | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification` | How to verify the output | No | `"attestation"` | +| `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | | `time` | `TimeLength` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | @@ -252,11 +194,7 @@ The global defaults object contains options for the Workflow itself, as well as ## 3.1 Fields -### 3.1.1 Version - -The version of the IPVM Workflow - -### 3.1.2 Secret Flag +### 3.1.1 Secret Flag The `secret` flag marks a task as being unsuitable for publication. @@ -264,29 +202,25 @@ If the `sceret` field is explicitely set, the task MUST be treated per that sett Note: there is no way to enforce secrecy at the task-level, so such tasks SHOULD only be negotiated with runners that are trusted. If secrecy must be inviolable, consider with [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) or [fully homomorphic encryption (FHE)](https://en.wikipedia.org/wiki/Homomorphic_encryption#Fully_homomorphic_encryption) inside the task. -### 3.1.3 Verification Strategy - -The OPTIONAL `check` field MUST supply a verification strategy if present. If omitted, it MUST default to Attestation. +### 3.1.2 Verification Strategy -FIXME +The OPTIONAL `check` field MUST supply a verification strategy if present. If omitted, it MUST default to `"attestation"`. ### 3.1.4 Time Quota -The `time` field configures the upper limit in wall-clock time that the executor SHOULD allow. +The OPTIONAL `time` field configures the upper limit in wall-clock time that the executor SHOULD allow. ### 3.1.5 Memory Quota -The `memory` field configures the upper limit in system memory that the executor SHOULD allow. +The OPTIONAL `memory` field configures the upper limit in system memory that the executor SHOULD allow. ### 3.1.6 Disk Quota -The `disk` field configures the upper limit in system memory that the executor SHOULD allow. +The OPTIONAL `disk` field configures the upper limit in system memory that the executor SHOULD allow. ### 3.1.7 Gas Quota -The `disk` field configures the upper limit in Wasm gas that the executor SHOULD allow. - --- FIXME configuarble gas schedule? +The OPTIONAL `disk` field configures the upper limit in Wasm gas that the executor SHOULD allow. ## 3.2 IPLD Schema @@ -299,38 +233,6 @@ type SystemConfig struct { disk Integer gas Integer } - -type Verification union { - | Oracle - | Consensus - | Optimistic - | ZKP -} representation keyed - -type Oracle union { - | Attestation "attestation" - | ThirdParty(DID) -} - -type Optimistic struct { - confirmations Integer - referee Referee -} - -type Referee enum { - | ZK(ZeroKnowledge) - | Trusted(DID) -} - -type Consensus struct { - agents [DID] -} - -type ZKP enum { - | Groth16 - | Nova - | Nova2 -} ``` ## 3.3 JSON Examples @@ -346,23 +248,20 @@ type ZKP enum { } ``` -# 4 Task Envelope +# 4 Task Configuration > With hands of iron, there's not a task we couldn't do > > — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor -While an indivdual invocation is structured like an AST (and eventually memoized as such), the tasks in a workflow spec MAY be unordered. Execution order MUST be determined by the scheduler and implied from the inputs. - -Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a Wasm module, or effects like an HTTP `GET` request. +Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a deterministic Wasm module, or [effects](fixme) like an HTTP `GET` request. Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. -Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. -IPVM Tasks are defined as an extension of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. +IPVM Tasks are defined as a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. -An IPVM Task MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `inputs` field. As such, the URI and command to be run are handled at the Action layer. +!! FIXME link to invocation main -A Task is a UCAN Action. A `meta["ipvm/config"]` field MAY be used to configure IPVM for this task. +Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual Task configuration MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `meta['ipvm/confg']` field. ## 4.1 JSON Examples @@ -384,13 +283,12 @@ A Task is a UCAN Action. A `meta["ipvm/config"]` field MAY be used to configure } ``` -Deterministic WebAssembly +## 4.3 JSON Examples ``` js { - // Clean, but possible a bridge too far. Probably handle this in an implcit like CIDs "supply-gas": { - "with": "gas:reserve://mine", // Or something... needs work at least + "with": "gas:reserve://mine", // Or something... needs work at least FIXME "do": "gas/supply", "inputs": { "on": ["/", "some-wasm"], @@ -418,11 +316,9 @@ Deterministic WebAssembly } ``` -Docker - ``` json { - "with": "docker:1:Qm12345", // Or something... wasm:Qm12345? + "with": "bafkreidvq3uqoxcxr44q5qhgdk5zk6jvziipyxguirqa6tkh5z5wtpesva", "do": "docker/run", "inputs": { "func": "calculate", @@ -512,7 +408,17 @@ type Measure union { [400, "nano", "seconds"] ``` -# 5 Related Work and Prior Art +# 5 Exception Handler + +Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. + +It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). + +Each task MAY include a failure workflow to run on failure. + +Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. + +# 6 Related Work and Prior Art * [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) * [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) @@ -532,7 +438,7 @@ AquaVM It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), -# 6 Acknowledgments +# 7 Acknowledgments * Steven Allen * Melanie Riise From cc4d1dfe6cdc9a9d54512ce8edee842c8e57bb11 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 21:37:36 -0800 Subject: [PATCH 32/42] More tighteninhg up of just the workflwos --- README.md | 9 +++++ workflow/README.md | 91 +++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index d6b6b66..b335d98 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,11 @@ type ZKP enum { * [Irakli Gozalishvili](https://github.com/Gozala), DAG House * [Hugo Dias](https://github.com/hugomrdias), DAG House * [Mikeal Rogers](https://github.com/mikeal/), DAG House +* Steven Allen +* Melanie Riise +* Christine Lemmer-Webber +* Peter Alvaro +* Juan Benet # 4 Prior Art @@ -306,7 +311,11 @@ type ZKP enum { * BucketVM (UCAN Invocation) * [WarpForge "Formula" v1](https://github.com/warpfork/warpforge/blob/master/examples/110-formula-usage/example-formula-exec.md) * [Bacalhau Job Spec](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L192) +Bloom +AquaVM +PACT/HydroLogic +It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), # FIXME STASH diff --git a/workflow/README.md b/workflow/README.md index d1f2adf..29c5565 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -29,7 +29,7 @@ An IPVM Workflow is a declarative cofiguration. A Workflow provides everything r > > J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) -The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike a system like WASI, there is a strict separation of effects from pure data, with no intermixing of computation with [promise pipelining](http://erights.org/elib/distrib/pipeline.html) (essentially distributed pipes). +The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike s systems like WASI, there is a strict separation of effects from pure data, an emphasis on verifiability, and [promise pipelining](http://erights.org/elib/distrib/pipeline.html). IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and verification. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. @@ -181,17 +181,19 @@ type Listeners struct { # 3 Configuation -The IPVM configuration struct sets secrecy, quotas, and verification strategy. +The IPVM configuration struct defines secrecy, quotas, and verification strategy: | Field | Type | Description | Required | Default | |----------|-------------------|-----------------------------------------|----------|--------------------------| | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | +| `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | | `time` | `TimeLength` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | | `gas` | `Integer` | Gas limit | No | `1000` | +This MAY be applied to individual Tasks or set as global defaults. + ## 3.1 Fields ### 3.1.1 Secret Flag @@ -254,10 +256,9 @@ type SystemConfig struct { > > — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor -Tasks are the smallest unit of negotiated work in an IPVM workflow. Each Task is restricted to a single type, such as a deterministic Wasm module, or [effects](fixme) like an HTTP `GET` request. Tasks describe everything required to the negotate the of work. While all Tasks share some things in common, the details MAY be quite different. - +Tasks are the smallest unit work in an IPVM workflow. Tasks describe everything required to the negotate the of work. While all Tasks share some fields, the details MAY be quite different based on the resorce and action being taken. Each Task is restricted to a single [safety level](FIXME), such as a deterministic Wasm module, or [effects](FIXME) like an HTTP `GET` request or Docker container, with no ability to intermix the two directly. -IPVM Tasks are defined as a subtype of [UCAN Actions](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. +IPVM Tasks are defined as a subtype of [UCAN Tasks](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. !! FIXME link to invocation main @@ -344,9 +345,21 @@ Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual } ``` -# 5 Appendix +# 5 Exception Handler + +Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. + +It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). + +Each task MAY include a failure workflow to run on failure. + +Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. -## 5.1 Support Types +# 6 Receipts + +# 7 Appendix + +## 6.1 Support Types ``` ipldsch type TimeUnit enum { @@ -408,46 +421,32 @@ type Measure union { [400, "nano", "seconds"] ``` -# 5 Exception Handler +# 8 Related Work and Prior Art -Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. +The [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) spec is a -It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). +BucketVM and `w3-machines` are two approaches from [DAG House](https://dag.house) to extend UCAN to invocations and workflows. At time of writing, both approaches are focused on invocation inside a cloud microservice deployment. Configuration is not required, as jobs are not negotiated. -Each task MAY include a failure workflow to run on failure. +[WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) describe how to reproducably build and cache packages. The functionality is a specialization of IPVM workflows, and may be configurable with IPVM in the future. -Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. +[AWS Lambda Workflows](https://docs.aws.amazon.com/amazonswf/latest/developerguide/swf-dg-create-workflow.html) + +[GitHub Workflows](https://docs.github.com/en/actions/using-workflows) + +[Cloud Native Builpacks](https://buildpacks.io/) preconfigure and + +[Open Container Initiative (OCI)](https://opencontainers.org/) (https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md) + +[Project Naiad](https://www.microsoft.com/en-us/research/video/introducing-project-naiad-and-differential-dataflow/) + +# 9 Acknowledgments + +[Luke Marsden](https://github.com/lukemarsden) for a long fateful discussion while [stuck on a tarmac](https://www.theguardian.com/world/2022/nov/04/spanish-airspace-partially-closed-as-chinese-rocket-debris-falls-to-earth) about how to make IPVM and Bacalhau work more closely together. + +Many thanks to [Quinn Wilton](https://github.com/QuinnWilton) for her review of the spec, suggestions + +Many thanks to [Irakli Gozalishvili](https://github.com/Gozala) for + +[Blaine Cook](https://github.com/blaine) -# 6 Related Work and Prior Art - -* [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) -* [BucketVM](https://purrfect-tracker-45c.notion.site/bucket-vm-73c610906fe44ded8117fd81913c7773) -* [WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) - -AWS Lambda workflows -GH Workflows -OCI -E Language -CapNet -Project Naiad -Bloom -PACT/HydroLogic -BucketVM -Bacalhau -AquaVM - -It is not possible to mention the separation of effects from computation without mentioning the algebraic effect lineage from Haskell, OCaml, and Eff. While the overall system looks quite different from the their type-level effects, this work owes a debt to at least Gordon Plotkin and John Power's work on [computational effects](https://homepages.inf.ed.ac.uk/gdp/publications/Overview.pdf), - -# 7 Acknowledgments - -* Steven Allen -* Melanie Riise -* Christine Lemmer-Webber -* Peter Alvaro -* Juan Benet -* Mark Miller -* Blaine Cook -* Luke Marsden -* Quinn Wilton -* Zeeshan Lakhani -* Irakli Gozalishvili +[Zeeshan Lakhani](https://github.com/zeeshanlakhani) From 0bfafcae000971795d1225344329ba733e29c035 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 22:32:24 -0800 Subject: [PATCH 33/42] Change based on clarifications about IPLD Schema from rvagg --- workflow/README.md | 47 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 29c5565..dd48178 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -56,7 +56,7 @@ The outer wrapper of a workflow MUST contain the following fields: |------------|---------------------|-------------------------------------------------------------------------------------|----------|---------| | `v` | `"0.1.0"` | IPVM workflow version | Yes | | | `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | -| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `null` | +| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `Null` | | `config` | `Config` | | No | `{}` | | `defaults` | `Config` | | No | `{}` | | `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | @@ -98,21 +98,17 @@ The `exception` field contains a Task with predefined inputs. See the [Exception ``` ipldsch type SignedWorkflow struct { - wfl Workflow - sig VarSig + work Workflow (rename "ipvm/workflow") + sig VarSig } type Workflow struct { - v SemVer - meta {String : Any} (implicit {}) - parent nullable &Task (implicit Null) - defauts SystemConfig (implicit {}) - tasks UCAN.Invocation - on Listeners (implicit {}) -- FIXME or just excpetion? -} - -type Listeners struct { - exception &Wasm nullable (implicit Null) + v SemVer + meta {String : Any} (implicit {}) + parent nullable &Task (implicit Null) + defauts SystemConfig (implicit {}) + tasks UCAN.Invocation + exception nullable &Wasm (implicit Null) } ``` @@ -134,9 +130,7 @@ type Listeners struct { "memory": [10, "mega", "bytes"] } }, - "on": { - "exception": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe" - }, + "exception": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe", "tasks": "ucan/invoke": { "v": "0.1.0", "nnc": "02468", @@ -187,7 +181,7 @@ The IPVM configuration struct defines secrecy, quotas, and verification strategy |----------|-------------------|-----------------------------------------|----------|--------------------------| | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | | `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | -| `time` | `TimeLength` | Timeout | No | `[5, "minutes"]` | +| `time` | `TimeInterval` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | | `gas` | `Integer` | Gas limit | No | `1000` | @@ -230,9 +224,9 @@ The OPTIONAL `disk` field configures the upper limit in Wasm gas that the execut type SystemConfig struct { secret Boolean (implicit False) check Verification (implicit Attestation) - time Time - memory Integer - disk Integer + time TimeInterval + memory InfoSize + disk InfoSize gas Integer } ``` @@ -390,7 +384,6 @@ type SIPrefix enum { | Milli "m" | Centi "c" | Deci "d" - | Unity | Deca "da" | Hecto "ha" | Kilo "k" @@ -401,20 +394,20 @@ type SIPrefix enum { | Exa "E" } -type TimeLength struct { - magnitude integer - prefix Prefix (implicit Unity) +type TimeInterval struct { + magnitude Integer + prefix optional Prefix unit TimeUnit } representation tuple type InfoSize struct { - magnitude integer - prefix Prefix (implicit Unity) + magnitude Integer + prefix optional Prefix unit InfoUnit } representation tuple type Measure union { - | TimeLength + | TimeInterval | InfoSize } From e1578b486bf6bd9f69b2cf84cd54d21b834505a2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 29 Nov 2022 23:33:32 -0800 Subject: [PATCH 34/42] Add links --- README.md | 9 ++-- workflow/README.md | 105 +++++++++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index b335d98..1491d1b 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * Description Formats * [Workflow](./workflow/README.md) - * [Task](./task/README.md) - * [Effect](./effect/README.md) - * [UCAN Invocation](https://github.com/ucan-wg/invocation) + * [Task](./workflow/README.md#4-task-configuration) + * [Host Managed Effects](./effect/README.md) * Runtime * Distributed Scheduler * Planner @@ -109,6 +108,10 @@ Partial failure in a deterministic system is simplified by using transactional s > > — Jerome Saltzer & M. Frans Kaashoek, Principles of Computer System Design +> 8. A programming language is low level when its programs require attention to the irrelevant. +> +> — Alan Perlis, Epigrams on Programming + While higher-level interfaces over IPVM Workflows MAY be used, ultimately configuration is the UI at this level of abstraction. The core use cases are moving workflows and tasks between machines, logging, and execution. IPVM Workflows aim to provide a computational model with a clear contract ("few if any surprises") for the programmer, while limiting verbosity. IPVM workflows follow the [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) philosophy with defaults and cascading configuration. ## 1.3 Security Considerations diff --git a/workflow/README.md b/workflow/README.md index dd48178..5c41809 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -35,10 +35,6 @@ IPVM Workflows MUST be suitable for the proposal of workflows and negotiation wi ## 1.1 Design Philosophy -> 8. A programming language is low level when its programs require attention to the irrelevant. -> -> — Alan Perlis, Epigrams on Programming - While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Workflows are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows with the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). @@ -80,19 +76,17 @@ The OPTIONAL `parent` field contains the CID of the IPVM Task that initiated it The OPTIONAL global `config` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. -## 2.1.4 Defaults +## 2.1.5 Defaults The OPTIONAL global `defaults` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. -## 2.1.8 Tasks +## 2.1.6 Tasks The `tasks` field contains all of the IPVM [Tasks](FIXME) set to run in this Workflow, each labelled by a human-readable key. -## 2.1.9 Listeners +## 2.1.7 Exception Handler -The OPTIONAL `on` map MAY contain an `exception` field. - -The `exception` field contains a Task with predefined inputs. See the [Exception Handling](FIXME) section for more. +The OPTIONAL `exception` field contains a Task with predefined inputs. See the [Exception Handling](#7-exception-handling) section for more. ## 2.2 IPLD Schema @@ -106,7 +100,8 @@ type Workflow struct { v SemVer meta {String : Any} (implicit {}) parent nullable &Task (implicit Null) - defauts SystemConfig (implicit {}) + global Config (implicit {}) + defauts Config (implicit {}) tasks UCAN.Invocation exception nullable &Wasm (implicit Null) } @@ -119,22 +114,20 @@ type Workflow struct { "ipvm/workflow": { "v": "0.1.0", "meta": { - "parent": null, - } + "tags": ["fission", "bacalhau", "dag-house"] + }, + "global": { + "time": [10, "minutes"], + }, "defaults": { - "global": { - "time": [10, "minutes"], - }, - "task": { - "gas": 1000, - "memory": [10, "mega", "bytes"] - } + "gas": 1000, + "memory": [10, "mega", "bytes"] }, "exception": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe", "tasks": "ucan/invoke": { "v": "0.1.0", "nnc": "02468", - "prf": [ -- FIXME having to resend this is a pain! + "prf": [ // FIXME having to resend this is a pain! {"/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy"}, {"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"} ], @@ -151,7 +144,7 @@ type Workflow struct { ], "meta": { "ipvm/config": { - "time": {"seconds": "100"}, + "time": {"minutes": "30"}, "secret": true } } @@ -181,12 +174,12 @@ The IPVM configuration struct defines secrecy, quotas, and verification strategy |----------|-------------------|-----------------------------------------|----------|--------------------------| | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | | `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | -| `time` | `TimeInterval` | Timeout | No | `[5, "minutes"]` | +| `time` | `TimeInterval` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` | | `gas` | `Integer` | Gas limit | No | `1000` | -This MAY be applied to individual Tasks or set as global defaults. +This MAY be set globally or configured on [individual Tasks](#4-task-configuration). ## 3.1 Fields @@ -224,10 +217,10 @@ The OPTIONAL `disk` field configures the upper limit in Wasm gas that the execut type SystemConfig struct { secret Boolean (implicit False) check Verification (implicit Attestation) - time TimeInterval - memory InfoSize - disk InfoSize - gas Integer + gas Integer (implicit 0) + time optional TimeInterval + memory optional InfoSize + disk optional InfoSize } ``` @@ -237,10 +230,10 @@ type SystemConfig struct { { "secret": true, "check": {"optimistic": {"confirmations": 2, "referee": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4"}}, + "gas": 5000, "time": [45, "minutes"], "memory": [500, "kilo", "bytes"], - "disk": [20, "mega", "bytes"], - "gas": 5000 + "disk": [20, "mega", "bytes"] } ``` @@ -252,11 +245,9 @@ type SystemConfig struct { Tasks are the smallest unit work in an IPVM workflow. Tasks describe everything required to the negotate the of work. While all Tasks share some fields, the details MAY be quite different based on the resorce and action being taken. Each Task is restricted to a single [safety level](FIXME), such as a deterministic Wasm module, or [effects](FIXME) like an HTTP `GET` request or Docker container, with no ability to intermix the two directly. -IPVM Tasks are defined as a subtype of [UCAN Tasks](https://github.com/ucan-wg/invocation/blob/rough/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. +IPVM Tasks are defined as a subtype of [UCAN Tasks](https://github.com/ucan-wg/invocation/blob/main/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. -!! FIXME link to invocation main - -Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual Task configuration MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `meta['ipvm/confg']` field. +Tasks MAY be configured in aggragate in the [global defaults](#215-defaults). Individual Task configuration MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `meta['ipvm/confg']` field. ## 4.1 JSON Examples @@ -305,7 +296,10 @@ Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual "ipvm/config": { "v": "0.1.0", "secret": false, - "check": {"optimistic": 2} + "check": { + "optimistic": 17, + "referee": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4" + } } } } @@ -313,7 +307,7 @@ Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual ``` json { - "with": "bafkreidvq3uqoxcxr44q5qhgdk5zk6jvziipyxguirqa6tkh5z5wtpesva", + "with": "ipfs://bafkreidvq3uqoxcxr44q5qhgdk5zk6jvziipyxguirqa6tkh5z5wtpesva", "do": "docker/run", "inputs": { "func": "calculate", @@ -331,10 +325,15 @@ Tasks MAY be configured in aggragate in the [global defaults](FIXME). Individual "$FOO": "bar" } }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "check": {"optimistic": 2} + "meta": { + "ipvm/config": { + "v": "0.1.0", + "secret": false, + "check": { + "optimistic": 2, + "referee": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4" + } + } } } ``` @@ -351,9 +350,11 @@ Note that effectful exception handlers that emit effects (such as network access # 6 Receipts + + # 7 Appendix -## 6.1 Support Types +## 7.1 Support Types ``` ipldsch type TimeUnit enum { @@ -368,8 +369,6 @@ type InfoUnit enum { | Bits | Nibble | Bytes - | Word32 - | Word64 } type Unit union { @@ -377,13 +376,16 @@ type Unit union { | InfoUnit } -type SIPrefix enum { +type SubPrefix enum { | Pico "p" | Nano "n" | Micro "u" | Milli "m" | Centi "c" | Deci "d" +} + +type SuperPrefix enum { | Deca "da" | Hecto "ha" | Kilo "k" @@ -394,15 +396,20 @@ type SIPrefix enum { | Exa "E" } +type SIPrefix union { + | SubPrefix + | SuperPrefix +} + type TimeInterval struct { magnitude Integer - prefix optional Prefix + prefix optional SIPrefix unit TimeUnit } representation tuple type InfoSize struct { magnitude Integer - prefix optional Prefix + prefix optional SubPrefix unit InfoUnit } representation tuple @@ -410,8 +417,14 @@ type Measure union { | TimeInterval | InfoSize } +``` + +## 7.1.1 JSON Examples +``` json [400, "nano", "seconds"] +[5, "seconds"] +[378, "exa", "bytes"] ``` # 8 Related Work and Prior Art From 4b93f49b565420d10b23672d91a67934e1c9c92b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 00:59:38 -0800 Subject: [PATCH 35/42] Really really getting quite close! Getting late... must sleep --- workflow/README.md | 54 ++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 5c41809..dff81a6 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -29,7 +29,7 @@ An IPVM Workflow is a declarative cofiguration. A Workflow provides everything r > > J. Paul Morrison, [Flow-Based Programming](https://jpaulm.github.io/fbp/book.html) -The potential complexity of a fully distributed execution by potentially unknown peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike s systems like WASI, there is a strict separation of effects from pure data, an emphasis on verifiability, and [promise pipelining](http://erights.org/elib/distrib/pipeline.html). +The potential complexity of a fully distributed execution by untrusted peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike s systems like WASI, there is a strict separation of effects from pure data, an emphasis on verifiability, and [promise pipelining](http://erights.org/elib/distrib/pipeline.html). IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and verification. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. @@ -78,15 +78,15 @@ The OPTIONAL global `config` object (FIXME section X.Y) sets the configuration f ## 2.1.5 Defaults -The OPTIONAL global `defaults` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. +The OPTIONAL `defaults` field configures default [configs](#3-configuration) for tasks. ## 2.1.6 Tasks -The `tasks` field contains all of the IPVM [Tasks](FIXME) set to run in this Workflow, each labelled by a human-readable key. +The `tasks` field contains all of the IPVM [Tasks](#4-task-configuration) set to run in this Workflow, each labelled by a human-readable key. ## 2.1.7 Exception Handler -The OPTIONAL `exception` field contains a Task with predefined inputs. See the [Exception Handling](#7-exception-handling) section for more. +The OPTIONAL `exception` field contains a Task with predefined inputs. See the [Exception Handling](#7-exception-handling) section for more deatil. ## 2.2 IPLD Schema @@ -243,34 +243,46 @@ type SystemConfig struct { > > — [The Protomen](https://en.wikipedia.org/wiki/The_Protomen), The Good Doctor -Tasks are the smallest unit work in an IPVM workflow. Tasks describe everything required to the negotate the of work. While all Tasks share some fields, the details MAY be quite different based on the resorce and action being taken. Each Task is restricted to a single [safety level](FIXME), such as a deterministic Wasm module, or [effects](FIXME) like an HTTP `GET` request or Docker container, with no ability to intermix the two directly. - -IPVM Tasks are defined as a subtype of [UCAN Tasks](https://github.com/ucan-wg/invocation/blob/main/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. +Tasks are the smallest level of work granularity a workflow. Tasks describe everything required to the negotate and execute all of the of work. IPVM Tasks are defined as a subtype of [UCAN Tasks](https://github.com/ucan-wg/invocation/blob/main/README.md#32-ipld-schema). Task types MAY require specific fields in the `inputs` field. Timeouts, gas, credits, transactional guarantees, result visibility, and so on MAY be separately confifured in the `ipvm/config` field. Tasks MAY be configured in aggragate in the [global defaults](#215-defaults). Individual Task configuration MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `meta['ipvm/confg']` field. -## 4.1 JSON Examples +Note that while all Tasks have a resource (URI) and action, the details MAY be quite different. Each Task is restricted to a specific [safety level](FIXME) based on its resource/action pair (such as a deterministic Wasm module or [effects](FIXME) like an HTTP `GET` request). Tasks MUST be scheduled according to its safety properties, which MAY have a performance impact. + +## 4.1 Fields + +Recall UCAN Invocation Tasks: + +| Field | Type | Description | Required | Default | +|----------|------------------|------------------------------------------------|----------|---------| +| `with` | `URI` | | Yes | | +| `do` | `Ability` | | Yes | | +| `inputs` | `Any` | | Yes | | +| `meta` | `{String : Any}` | Fields that will be ignored during memoization | No | `{}` | + +An OPTIONAL IPVM `Config` MAY be included at the `meta['ipvm/config']` path. If included, the `Config` MUST set the IPVM configuration for this Task, overwriting any of the fields on the envelope's top-level `defaults` field, or system-wide defaults. + +## 4.3 JSON Examples ``` json { - "with": "dns://example.com?TYPE=TXT", - "do": "crud/update", - "inputs": { - "value": "hello world" - }, - "meta": { - "ipvm/config": { - "secret": false, - "timeout": [500, "milli", "seconds"], - "retries": 5, - "verification": "attestation" + "simple": { + "with": "dns://example.com?TYPE=TXT", + "do": "crud/update", + "inputs": { + "value": "hello world" + }, + "meta": { + "ipvm/config": { + "secret": false + "timeout": [500, "milli", "seconds"], + "verification": "attestation" + } } } } ``` -## 4.3 JSON Examples - ``` js { "supply-gas": { From ef904a18fef20b5cab93a465fe2051b461712d3e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 09:00:47 -0800 Subject: [PATCH 36/42] Catch! --- workflow/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index dff81a6..42b5e6b 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -86,7 +86,7 @@ The `tasks` field contains all of the IPVM [Tasks](#4-task-configuration) set to ## 2.1.7 Exception Handler -The OPTIONAL `exception` field contains a Task with predefined inputs. See the [Exception Handling](#7-exception-handling) section for more deatil. +The OPTIONAL `catch` field contains a Task with predefined inputs. See the [Exception Handling](#7-exception-handling) section for more deatil. ## 2.2 IPLD Schema @@ -97,13 +97,13 @@ type SignedWorkflow struct { } type Workflow struct { - v SemVer - meta {String : Any} (implicit {}) - parent nullable &Task (implicit Null) - global Config (implicit {}) - defauts Config (implicit {}) - tasks UCAN.Invocation - exception nullable &Wasm (implicit Null) + v SemVer + meta {String : Any} (implicit {}) + parent nullable &Task (implicit Null) + global Config (implicit {}) + defauts Config (implicit {}) + tasks UCAN.Invocation + catch nullable &Wasm (implicit Null) } ``` @@ -123,7 +123,7 @@ type Workflow struct { "gas": 1000, "memory": [10, "mega", "bytes"] }, - "exception": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe", + "catch": "bafkreifsaaztjgknuha7tju6sugvrlbiwbyx5jf2pky2yxx5ifrpjscyhe", "tasks": "ucan/invoke": { "v": "0.1.0", "nnc": "02468", From a3de19d15fcb52b17f148da62bb11dfe2bc23566 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 18:38:31 -0800 Subject: [PATCH 37/42] Many thank-yous --- workflow/README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 42b5e6b..212f80f 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -31,7 +31,7 @@ An IPVM Workflow is a declarative cofiguration. A Workflow provides everything r The potential complexity of a fully distributed execution by untrusted peers is very high. IPVM Workflows reduce the number of possible states by forcing explicit handling of any dangerous effects. The IPVM Workflow spec is a declarative document that MAY be inspected, transmitted, logged, and negotiated. Unlike s systems like WASI, there is a strict separation of effects from pure data, an emphasis on verifiability, and [promise pipelining](http://erights.org/elib/distrib/pipeline.html). -IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with provuders on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and verification. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. +IPVM Workflows MUST be suitable for the proposal of workflows and negotiation with providers on a discovery layer (ahead of credential delegation), execution on untrusted peer machines, and verification. Workflows SHOULD provide a sufficiently expressive base to build more complex models such as actors, event-driven systems, map-reduce, and so on. ## 1.1 Design Philosophy @@ -441,30 +441,28 @@ type Measure union { # 8 Related Work and Prior Art -The [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) spec is a +The [Bacalhau Job (Alpha)](https://github.com/filecoin-project/bacalhau/blob/8568239299b5881bc90e3d6be2c9aa06c0cb3936/pkg/model/job.go#L113-L126) spec is a complete runner spec for Docker, Wasm, and Python source. At time of writing, it runs on a volunteer network, and has plans to integrate an authority layer. -BucketVM and `w3-machines` are two approaches from [DAG House](https://dag.house) to extend UCAN to invocations and workflows. At time of writing, both approaches are focused on invocation inside a cloud microservice deployment. Configuration is not required, as jobs are not negotiated. +BucketVM and [`w3-machines`](https://github.com/web3-storage) are two approaches from [DAG House](https://dag.house) to extend UCAN to invocations and workflows. At time of writing, both approaches are focused on invocation inside a cloud microservice deployment. Configuration is not required, as jobs are not negotiated. -[WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) describe how to reproducably build and cache packages. The functionality is a specialization of IPVM workflows, and may be configurable with IPVM in the future. - -[AWS Lambda Workflows](https://docs.aws.amazon.com/amazonswf/latest/developerguide/swf-dg-create-workflow.html) +[Cloud Native Builpacks](https://buildpacks.io/) are descriptions of an environment that stack together. They output an [OCI](https://opencontainers.org/) container. -[GitHub Workflows](https://docs.github.com/en/actions/using-workflows) +[GitHub Workflows](https://docs.github.com/en/actions/using-workflows) is a configuration to run one or more jobs, hooked into events on their platform. Workflows can be composed out of discrete actions or from other workflows. -[Cloud Native Builpacks](https://buildpacks.io/) preconfigure and +[Lambda Workflows](https://docs.aws.amazon.com/amazonswf/latest/developerguide/swf-dg-create-workflow.html) are a serverless workflow layer built on top of AWS, and thus integrates with their other offerings like IAM, S3, payments, and so on. -[Open Container Initiative (OCI)](https://opencontainers.org/) (https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md) +[Project Naiad](https://www.microsoft.com/en-us/research/video/introducing-project-naiad-and-differential-dataflow/) and its lineage (e.g. [Timely Dataflow](https://timelydataflow.github.io/timely-dataflow/), [Differential Dataflow](https://timelydataflow.github.io/differential-dataflow/)) offer an extremely powerful dataflow model, including differential updates, control flow cycles, long running processes, but in a trusted environment. Such features could be supported for a subset of task types in IPVM in the future. -[Project Naiad](https://www.microsoft.com/en-us/research/video/introducing-project-naiad-and-differential-dataflow/) +[WarpForge Formulas](https://github.com/warptools/warpforge/blob/master/examples/100-formula-parse/example-formulas.md) describe how to reproducably build and cache packages. The functionality is a specialization of IPVM workflows, and may be configurable with IPVM in the future. # 9 Acknowledgments [Luke Marsden](https://github.com/lukemarsden) for a long fateful discussion while [stuck on a tarmac](https://www.theguardian.com/world/2022/nov/04/spanish-airspace-partially-closed-as-chinese-rocket-debris-falls-to-earth) about how to make IPVM and Bacalhau work more closely together. -Many thanks to [Quinn Wilton](https://github.com/QuinnWilton) for her review of the spec, suggestions +Thanks to [James Walker](https://github.com/walkah) for helping draw parallels between CIDs, IPLD, and raw bytes for promises in support of complex data pipelines. -Many thanks to [Irakli Gozalishvili](https://github.com/Gozala) for +Many thanks to [Quinn Wilton](https://github.com/QuinnWilton) for her review of the spec, suggesting terms that would be most familiar to developers, talking through how to make the exception handling useful-but-safe in a static workflow, and suggesting further work involving fixed point computation. -[Blaine Cook](https://github.com/blaine) +Many thanks to [Irakli Gozalishvili](https://github.com/Gozala) for the long discussions about invocation needs at [DAG House](https://dag.house), keeping the conversation grounded in a capabilities worldview, and for the many, many comments on various iterations of this spec across two repos. -[Zeeshan Lakhani](https://github.com/zeeshanlakhani) +Thanks to [Blaine Cook](https://github.com/blaine) for several conversations about developer expectations and effect systems. From 167250dc8170620b80f28d39d439ecbdf8e817c8 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 18:45:22 -0800 Subject: [PATCH 38/42] Fix many but not all spelling issues --- .github/workflows/words-to-ignore.txt | 53 +++++++++++++++++++++++++++ workflow/README.md | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/words-to-ignore.txt b/.github/workflows/words-to-ignore.txt index c971691..17513f5 100644 --- a/.github/workflows/words-to-ignore.txt +++ b/.github/workflows/words-to-ignore.txt @@ -1,37 +1,71 @@ ABI +ACM +Agoric Antigoals +AquaVM +Atomics +Auth Autocodec BYOL Bacalhau +Berkley BucketVM +CHa CIDs CLA CapTP +Config +DNS +DSLs +Ericsson FHE FS FVM Filecoin +Frans HydroLogic +IAM IPC IPFS IPFS-FAN IPLD IPLI +IPNS IPVM JIT +JSON +Kaashoek +Lampson +Lemmer-Webber +Lifecycle Linearizability MERCHANTABILITY Memoized Memoizing OCAP +OCaml +OCapN +Perlis +Plotkin PoPs Pre-Draft Prenegotiated README +Requestor +Riise Roadmap SPDX-License-Identifier SPKI +STM +Saltzer +SemVer +Spiritely +TTL +Transactionality +UC UCAN +UI +URI VM WASI Wasm @@ -39,6 +73,7 @@ Wasm-on-IPFS WebAssembly Zelenka acceptor +acyclic behaviours codec codecs @@ -47,11 +82,16 @@ cron dataflow de decrypt +decrypted defunctionalization +dereference +dereferencing +effectful effectfulness enqueuing expede facto +hardcoded idempotence individuals' inspectable @@ -60,25 +100,38 @@ md memoization merchantability micropayment +microservice middleware modelled +namespace no_good_woman non-effectful non-sublicensable +nontermination ocap others' +patentable pipelining +pre-resolved +preimage prenegotiated +repos +requestor runtimes +serverless sexualized signalling socio-economic spiral_calendar +struct subjobs +subtype tradeoff transferee +trustless ucan-chan untrusted +v0 wasm-ipfs woman_scientist world_map diff --git a/workflow/README.md b/workflow/README.md index 212f80f..f67d9f6 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -35,7 +35,7 @@ IPVM Workflows MUST be suitable for the proposal of workflows and negotiation wi ## 1.1 Design Philosophy -While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Workflows are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the decalarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. +While IPVM in aggregate is capable of executing arbitrary programs, individual IPVM Workflows are specified declaratively, and tasks workflows MUST be acyclic. Invocation in the declarative style liberates the programmer from worrying about explicit sequencing, parallelism, memoization, distribution, and nontermination in a trustless settings. Such constraints also grants the runtime control and flexibility to schedule tasks in an efficient and safe manner. These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows with the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). From e6ec07aeb668c5c92bfd1a53e1ea2b6452afd353 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 21:48:00 -0800 Subject: [PATCH 39/42] Rewire on catch --- workflow/README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index f67d9f6..7352482 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -260,7 +260,7 @@ Recall UCAN Invocation Tasks: | `inputs` | `Any` | | Yes | | | `meta` | `{String : Any}` | Fields that will be ignored during memoization | No | `{}` | -An OPTIONAL IPVM `Config` MAY be included at the `meta['ipvm/config']` path. If included, the `Config` MUST set the IPVM configuration for this Task, overwriting any of the fields on the envelope's top-level `defaults` field, or system-wide defaults. +An OPTIONAL IPVM `Config` MAY be included at the `meta['ipvm/config']` path. The `meta` field SHOULD not captured as part of task memoization, so this informtaion will be omitted from the distributed invocation table. If included, the `Config` MUST set the IPVM configuration for this Task, overwriting any of the fields on the envelope's top-level `defaults` field, or system-wide defaults. ## 4.3 JSON Examples @@ -352,17 +352,28 @@ An OPTIONAL IPVM `Config` MAY be included at the `meta['ipvm/config']` path. If # 5 Exception Handler -Note that while IPVM MUST treat the pure tasks together as transactional, it is not possible to roll back any destructive effects that have been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect. +If present, the OPTIONAL `catch` field MUST be run in response to a `Task` returning on the `Failure` branch. The determinitsic & pure Wasm module MUST take a `Failure` object as input, and MUST return data in the following shape: -It is often desirable to fire a specific workflow in the case that a workflow fails. Such cases MAY include wall-clock timeouts, running out of gas, loss of network access, or ___, among others. The exception handler fills a similar role to [GenServer.handle_info/2](https://hexdocs.pm/elixir/1.14.2/GenServer.html#c:handle_info/2). +``` ipldsch +type Handle union { + | String "rewire" -- Task name inside the current Workflow + | String "msg" -- Format the error message and panic +} +``` -Each task MAY include a failure workflow to run on failure. +If the `msg` branch is returned, the invocation MUST immedietly rethrow with the update message. -Note that effectful exception handlers that emit effects (such as network access) MAY fail for the same reason as the workflow that caused the exception to be thrown. Running a pure value is RECOMMENDED. +Note that while IPVM MUST treat the pure tasks together as transactional. It is not possible to roll back any destructive effects that have already been run. As such, it is RECOMMENDED to have few (if any) tasks depend on the output of a destructive effect, so they can be scheduled at the end of the workflow. -# 6 Receipts +# 6 Receipt Output +| Field | Type | Description | Required | Default | +|--------|-----------------|-----------------------------------------------------------------------|----------|---------| +| `inv` | `&Invocation` | CID of the Invocation that generated this response | Yes | | +| `out` | `{String: Any}` | The results of each call, the task's label. MAY contain sub-receipts. | Yes | | +| `meta` | `Any` | Non-normative extended fields | No | `null` | +If the `catch` field is set on the outer `Workflow`, The `out` field MAY include the output under the `ipvm/catch` key # 7 Appendix From b40ef5dbe8090a945795c780078795f2be3971c1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 21:51:09 -0800 Subject: [PATCH 40/42] Eliminate infinite loop --- workflow/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 7352482..1aad563 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -356,9 +356,10 @@ If present, the OPTIONAL `catch` field MUST be run in response to a `Task` retur ``` ipldsch type Handle union { - | String "rewire" -- Task name inside the current Workflow - | String "msg" -- Format the error message and panic -} + | Success "ok" -- End task with Success object + | String "rewire" -- Task name inside the current Workflow + | String "msg" -- Format the error message and panic +} respresentation keyed ``` If the `msg` branch is returned, the invocation MUST immedietly rethrow with the update message. From 22a827f710fd33565dab7e57e440980dfcdda52a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 22:13:21 -0800 Subject: [PATCH 41/42] Wrapping up cleanup --- workflow/README.md | 63 +++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/workflow/README.md b/workflow/README.md index 1aad563..352fa92 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -21,7 +21,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # 0 Abstract -An IPVM Workflow is a declarative cofiguration. A Workflow provides everything required to execute one or more tasks: defaults, tasks and their dependencies, authorization, metadata, signatures, and so on. +An IPVM Workflow is a declarative cofiguration that extends a [UCAN Invocation](https://github.com/ucan-wg/invocation). A Workflow provides everything required to execute one or more tasks: defaults, tasks and their dependencies, authorization, metadata, signatures, and so on. # 1 Introduction @@ -48,15 +48,15 @@ The outer wrapper of a workflow MUST contain the following fields: | `ipvm/workflow` | `Workflow` | IPVM Workflow | Yes | | `signature` | `VarSig` | [VarSig](https://github.com/ChainAgnostic/varsig/) of serialized fields | Yes | -| Field | Type | Description | Required | Default | -|------------|---------------------|-------------------------------------------------------------------------------------|----------|---------| -| `v` | `"0.1.0"` | IPVM workflow version | Yes | | -| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | -| `parent` | `&Workflow or Null` | The CID of the initiating workflow (if any) FIXME probably want the task & workflow | No | `Null` | -| `config` | `Config` | | No | `{}` | -| `defaults` | `Config` | | No | `{}` | -| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | -| `on` | `Listeners` | IPVM event listeners | No | `{}` | +| Field | Type | Description | Required | Default | +|------------|------------------------------|------------------------------------------------------------------------|----------|---------| +| `v` | `"0.1.0"` | IPVM workflow version | Yes | | +| `meta` | `{String : Any}` | User-defined object (tags, comments, etc) | No | `{}` | +| `parent` | `[&Workflow, Label] or Null` | The workflow & task label that initiated the current workflow (if any) | No | `Null` | +| `config` | `Config` | Global configuration (e.g. timeout for the entire workflow) | No | `{}` | +| `defaults` | `Config` | Individual task config defaults | No | `{}` | +| `tasks` | `UCAN.Invocation` | UCAN Invocation | Yes | | +| `catch` | `&WasmTask` | Deterministic Wasm that fires on exceptions | No | `{}` | ## 2.1 Fields @@ -74,7 +74,7 @@ The OPTIONAL `parent` field contains the CID of the IPVM Task that initiated it ## 2.1.4 Config -The OPTIONAL global `config` object (FIXME section X.Y) sets the configuration for the workflow itself, and defaults for tasks. +The OPTIONAL global [`config` object](#3-configuration) sets the configuration for the workflow itself, and defaults for tasks. ## 2.1.5 Defaults @@ -98,13 +98,18 @@ type SignedWorkflow struct { type Workflow struct { v SemVer - meta {String : Any} (implicit {}) - parent nullable &Task (implicit Null) - global Config (implicit {}) - defauts Config (implicit {}) + meta {String : Any} (implicit {}) + parent nullable TaskRef (implicit Null) + global Config (implicit {}) + defauts Config (implicit {}) tasks UCAN.Invocation - catch nullable &Wasm (implicit Null) + catch nullable &Wasm (implicit Null) } + +type TaskRef struct { + inv &Invocation + task String -- Label for the task +} representation tuple ``` ## 2.3 JSON Exmaples @@ -127,7 +132,7 @@ type Workflow struct { "tasks": "ucan/invoke": { "v": "0.1.0", "nnc": "02468", - "prf": [ // FIXME having to resend this is a pain! + "prf": [ {"/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy"}, {"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"} ], @@ -247,8 +252,6 @@ Tasks are the smallest level of work granularity a workflow. Tasks describe ever Tasks MAY be configured in aggragate in the [global defaults](#215-defaults). Individual Task configuration MUST be embedded inside of a [UCAN Action](https://github.com/ucan-wg/invocation)'s `meta['ipvm/confg']` field. -Note that while all Tasks have a resource (URI) and action, the details MAY be quite different. Each Task is restricted to a specific [safety level](FIXME) based on its resource/action pair (such as a deterministic Wasm module or [effects](FIXME) like an HTTP `GET` request). Tasks MUST be scheduled according to its safety properties, which MAY have a performance impact. - ## 4.1 Fields Recall UCAN Invocation Tasks: @@ -285,14 +288,6 @@ An OPTIONAL IPVM `Config` MAY be included at the `meta['ipvm/config']` path. The ``` js { - "supply-gas": { - "with": "gas:reserve://mine", // Or something... needs work at least FIXME - "do": "gas/supply", - "inputs": { - "on": ["/", "some-wasm"], - "max": 1000 - } - }, "some-wasm": { "with": "wasm:1:Qm12345", // Or something... wasm:Qm12345? "do": "ipvm/run", @@ -410,14 +405,14 @@ type SubPrefix enum { } type SuperPrefix enum { - | Deca "da" + | Deca "da" | Hecto "ha" - | Kilo "k" - | Mega "M" - | Giga "G" - | Tera "T" - | Peta "P" - | Exa "E" + | Kilo "k" + | Mega "M" + | Giga "G" + | Tera "T" + | Peta "P" + | Exa "E" } type SIPrefix union { From 957270102309bebec147c0d9410b338beab93c49 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 30 Nov 2022 22:33:36 -0800 Subject: [PATCH 42/42] Start cutting cruft --- README.md | 39 +----- effect/README.md | 305 --------------------------------------------- job/example.josn | 83 ------------ job/job.dot | 39 ------ job/job.json | 67 ---------- job/job.var1.json | 88 ------------- job/warpforge.json | 36 ------ workflow/README.md | 33 ++++- 8 files changed, 38 insertions(+), 652 deletions(-) delete mode 100644 effect/README.md delete mode 100644 job/example.josn delete mode 100644 job/job.dot delete mode 100644 job/job.json delete mode 100644 job/job.var1.json delete mode 100644 job/warpforge.json diff --git a/README.md b/README.md index 1491d1b..6db99ac 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * Description Formats * [Workflow](./workflow/README.md) * [Task](./workflow/README.md#4-task-configuration) - * [Host Managed Effects](./effect/README.md) + * Host-Managed Effects + * Capabilty Model + * SPKI + * OCap * Runtime * Distributed Scheduler * Planner @@ -33,9 +36,6 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * Lifecycle * Request * Negotiation - * Capabilty - * SPKI - * OCapN * Verification * Payment Channel * Wasm μKernel @@ -46,17 +46,14 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S * Randomness * HTTP * FVM - * Bacalhau # 0 Abstract -IPVM - -An IPVM "job" is a declarative description of WebAssembly and managed effects to be run by the IPVM runtime. +IPVM brings content addressing to computation, via a distributed runtime built on top of IPFS. It leverages the deterministic subset of WebAssembly plus a ukernel that supports IPFS content resolution, memoization, and adaptive optomization. # 1 Motivation -IPVM provides a deterministic-by-default, content addressed execution environment. Execution may always be run locally, but there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. +IPVM provides a deterministic-by-default, content addressed execution environment. Computation MAY be run locally or remotely. While local operation has zero latency, there are many cases where remote exection is desirable: access to large data, faster processors, trusted execution environments, or access to specialized hardware, among others. > Because he was talking (mainly) to a set of platform folks he admonished us to think about how we can build platforms that lead developers to write great, high performance code such that developers just fall into doing the “right thing”. Rico called this the Pit of Success. > @@ -77,30 +74,6 @@ Partial failure in a deterministic system is simplified by using transactional s # Stack Diagrram -``` -┌───────────────────────────────────────────────┬───────────────────────────┐ -│ │ │ -│ Human Configuration: │ │ -│ Defaults, Exception Handling, Comments, Tags │ │ -│ (IPVM Workflow) │ │ -│ │ Multi-Request Pipelining │ -├───────────────────────────────────────────────┤ (UCAN Invocation) │ -│ │ │ -│ IPVM Config, Verification Level, etc │ │ -│ (IPVM Task) │ │ -│ │ │ -├───────────────────────────────────────────────┴───────────────────────────┤ -│ │ -│ Call Graph │ -│ (UCAN Invocation) │ -│ │ -├───────────────────────────────────────────────────────────────────────────┤ -│ │ -│ Authority │ -│ (UCAN Core) │ -│ │ -└───────────────────────────────────────────────────────────────────────────┘ -``` ## 1.2 Humane Design diff --git a/effect/README.md b/effect/README.md deleted file mode 100644 index f2c8e80..0000000 --- a/effect/README.md +++ /dev/null @@ -1,305 +0,0 @@ - -## 5.1 Secrets - -Secrets MUST modelled as effects. Just as not every machine will have the ability to update a DNS record, not all will be able to decrypt data or sign data with a specific private key. These effects SHOULD default to private visibility. - -### 5.1.1 Signing - -| Field | Type | Description | Required | Default | -|----------|-----------------|---------------------------------|-------------|---------| -| `type` | `"ipvm/effect"` | Identify this job as a Wasm 1.0 | Yes | | -| `with` | DID | | Yes | | -| `do` | `"crypto/sign"` | | Yes | | -| `value` | String | | On mutation | | -| `public` | `Boolean` | RECOMMENDED not public | Yes | `false` | - - - -``` json -{ - "type": "ipvm/effect", - "with": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4", - "do": "crypto/sign", - "inputs": [{ "value": "aBcDeF" }] -} -``` - -### 5.1.2 Out-of-Band Decryption - -``` json -{ - "type": "ipvm/effect", - "with": "ipns://alice.fission.name/supersecret", - "do": "crypto/decrypt", - "public": false, - "inputs": [{ "value": "aBcDeF" }] -} -``` - -### 5.1.3 In-Band Secrets - -Some cases require having direct access to a secret, such as a - -``` json -{ - "type": "ipvm/effect", - "with": "ipvm:secret:github.com/ipvm-wg/spec?secret=API_KEY_NAME", - "do": "secret/get", - "public": false, - "inputs": [{ "value": "aBcDeF" }] -} -``` - -## 5.2 DNS - -| Field | Type | Description | Required | -|---------|---------------------|-----------------------------------------------------------------------|-------------| -| `type` | `"ipvm/effect/dns"` | Identify this job as a Wasm 1.0 | Yes | -| `with` | URI | DNS URI (domain name or subdomain) | Yes | -| `do` | crud | Any ability in the `crud` namespace (e.g. `crud/read`, `crud/update`) | Yes | -| `value` | String | | On mutation | - -More specific uses MAY be built out of the primitive DNS resolver. - - - -Read from [DNSLink](https://dnslink.io) - -``` json -{ - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" -} -``` - -Update an A record - -``` json -{ - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=A", - "do": "crud/update", - "value": "12.345.67.890" -} -``` - -[did:dns](https://danubetech.github.io/did-method-dns/) - -``` json -{ - "type": "ipvm/effect", - "with": "dns://_key1._did.example.com?TYPE=URI", - "do": "crud/read" -} -``` - -## 5.3 IPNS - -[IPNS](https://docs.ipfs.tech/concepts/ipns/) - -``` json -{ - "type": "ipvm/effect", - "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", - "do": "crud/read" -} -``` - -## 5.4 Randomness - -Randomness is RECOMMENDED to be souiderived from a trused high-entropy source, such as [drand](https://drand.love/). - -``` json -{ - "type": "ipvm/effect", - "with": "ipns://QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", - "do": "crud/read" -} -``` - - - - - - -NOTE TO SELF: on `crud/read`, we probably need some kind of max file size limit (and timeout obvs) - - - - - -# 4 Effects - -The contract for effects is different from pure computation. As effects by definition interact with the "real world". These may be either commands or queries. Exmaples of effects include reading from DNS, sending an HTTP POST request, running a WASI module with network access, or receieving a random value. - -The `with` field MAY be filled from a relative value (previous step) - -| Field | Type | Description | Required | Default | -|----------|---------|----------------------------|----------|---------| -| `v` | SemVer | IPVM effect schema version | No | `0.1.0` | -| `args` | `[{}]` | | No | `[]` | - - - -## 4.3 JSON Example - -``` json -{ - "using": "docker:1:Qm12345", // Or something... wasm:Qm12345? - "do": "executable/run", - "inputs": { - "func": "calculate", - "args": [ - 1, - "hello world", - {"c": {"ucan/promise": ["/", "some-other-action"]}}, - {"a": 1, "b": 2, "c": 3} - ], - "container": { - "entry": "/", - "workdir": "/", - }, - "env": { - "$FOO": "bar" - } - }, - "ipvm/config": { - "v": "0.1.0", - "secret": false, - "check": {"optimistic": 2} - } -} -``` - -## 2 Effects - -Tasks can be broken into categories: - -1. Pure -2. Effectful - * Destructive - * Nondestructive - -Where a pure function takes inputs to outputs, deterministically, without producing any other change to the world (aside from [heat](https://en.wikipedia.org/wiki/Second_law_of_thermodynamics)). - -Effects interact with the world in some way . Reading from a database, sending an email, and firing missiles are all kinds of effect. - -One way to represent the difference between these pictorally is with box-and-wire diagrams. Here computation is drawn as a box. Explicit input and output are drawn as horizontal arrows, with input on the left and output on the right. Effects are drawn as vertical-pointing arrows. - -``` - Safe - ▲ ┌─ ─┐ - │ │ ┌─────────────┐ │ - │ │ │ │ │ - │ │ │ │ │ - │ Pure │ ──────► ├────► │ - │ │ │ │ │ - │ │ │ │ │ - │ │ └─────────────┘ │ - │ └─ │ - │ │ - │ │ - │ │ Nondestructive - │ │ - │ │ - │ ┌─ ┌─ │ - │ │ │ │ ┌─────────────┐ │ - │ │ │ └───► │ │ - │ │ │ │ │ │ - │ │ Query │ ──────► ├────► │ - │ │ │ │ │ │ - │ │ │ │ │ │ - │ │ │ └─────────────┘ │ - │ │ └─ ─┘ - │ │ - │ │ - │ Effectful │ - │ │ - │ │ - │ │ ┌─ ▲ ─┐ - │ │ │ ┌─────────────┐ │ │ - │ │ │ │ ├──┘ │ - │ │ │ │ │ │ - │ │ Command │ ──────► ├────► │ Destructive - │ │ │ │ │ │ - │ │ │ │ │ │ - │ │ │ └─────────────┘ │ - ▼ └─ └─ ─┘ -Unsafe -``` - -FIXME safety level MUST be defined by the pair `(URI Scheme, Ability)` (service metadata). This may need a field on the workflow. - -## 2.1 Pure Functions - -Pure functions are very safe and simple. They can be retried safely, and their output is directly verifiable against other executions. Once a pure function has been accepted, it can be cached with an infinite TTL. The output of a pure function is fully equivalent to the invocation of the function and its arguments. - -Note that in IPVM, pre-resolved CID handles are treated as referentially transparent. See [CID Handles](FIXME). - -## 2.2 Nondestructive Effects - -For the pureposes of IPVM, nondestructive effects are modelled as coming from "the world", and can be treated as input. They are not pure, because they depend on things outside of the workflow such as read-only state and nondeterminsm. Since they are not guaranteed reproducable, they can change from one request to the next. While this kind of effect They can be thought of as "read only", since they only report from outside source, but do not change it. - -Nondestructive effects can be retried or raced safely. Each nondestructive invocation is unique, and need to be attested from a trusted source. Once their value enters the IPVM system, it is treated as a pure value. - -## 2.3 Destructive Effects - -Destructive effects are the opposite: they "update the world". Sending an text message cannot be retried without someone noticing a second text message. Destructive effects require careful handling, with attestation from the executor. Ensuring exact-once execution of destructive effects requires consensus on the execution schedule of the one task, which often incurs a performance penalty over other forms of task. - -# 3 Content Handles - -FIXME This probably belongs in its own spec. Now that we have the basic concept, it keeps coming up in conversation. - -[Content Identifiers](https://docs.ipfs.tech/concepts/content-addressing/) (CIDs) are integral to IPFS. They map a hash to its preimage, which is a stable identifier for it across all machines, liberating it from location. However, this does not guarantee that the CID is resolvable at a particular time or place. - -A Content Handle (CHa) is a type that MUST only be created by the runtime and MUST NOT have a serializated representation. This special handling provides a [lightweight proof](https://kataskeue.com/gdp.pdf) that the content is reachable to downstream tasks, allowing the scheduler to treat it as a pure value. A failure to dereference content from a CHa is a failure of the runner, not the requestor. By analogy to HTTP, failing to resolve a CHa is a 500, passing a malformed CID is a 400, and the effect converting a CID to a CHa timing out is a 408. - -This that the CID has been checked, and the runner guarintees that it is available in the current environment. - -NOTE TO SELF: should CHa be its own spec? Seems useful :thinking: - -| Issue | At Fault | -|-------------------------------|------------------------| -| Malformed CID | Requestor | -| Cannot provide all CHa blocks | Runner | -| Cannot resolve CID to CHa | Network or Environment | - -``` js -// Just a sketch for now, don't judge me! -{ - "type": "ipvm/effect", - "version": "0.1.0", - "using": "cid:Qm12345", - "do": "handle/resolve" -} -``` - -```haskell --- No, this won't survive into the fnal draft. Just stashing it here for now as a note! -resolve :: CID -> IO (Either Timeout CHa) -``` - -# 3 Pure Wasm - -Treated as a black box, the deterministic subset of Wasm MUST be treated as a pure function, with no additional handlers or other capabilities directly available via WASI or similar aside from the ability to read content addressed data. - -Note that while the function itself is pure, as is dereferencing content-addressed data, the function MAY fail if the CID is not available to the runner. - -The Wasm configuration MUST extend the core task type as follows: - -| Field | Type | Description | Required | Default | -|--------|-----------------------|-------------------------------------------|----------|---------| -| `v` | SemVer | The Wasm module's Wasm version | No | `0.1.0` | -| `func` | `String or OutputRef` | The function to invoke on the Wasm module | Yes | | -| `args` | `[{String : Any}]` | Arguments to the Wasm executable | Yes | | - -## 3.2 IPLDS Schema - -``` ipldsch -type WasmTask struct { - v SemVer - func String -- Function name to invoke - args [Any] -- Positional arguments FIXME **for now** -- we need to figure out WIT, I know -} -``` diff --git a/job/example.josn b/job/example.josn deleted file mode 100644 index 61b9eff..0000000 --- a/job/example.josn +++ /dev/null @@ -1,83 +0,0 @@ - -## 2.3.2 Effectful - -Here is an example of a nontrivial IPVM job which reads from DNS, performs several jobs on the value, and atomically performs a DNS update with the output value. - -``` json -{ - "type": "ipvm/job", - "version": "0.1.0", - "requestor": "did:key:zAlice", - "nonce": "xjd72gs_k", - "run": { - "read-dns": { - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" - }, - "check-dns": { - "type": "ipvm/effect", - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" - "inputs": [ - { "_": { "from": "end" } } - ] - }, - "write-dns": { - "type": "ipvm/effect", - "do": "crud/update", - "to": "dns://_dnslink.example.com?TYPE=TXT", - "args": [ - { "value": { "from": "end" } } - { "_": { "from": "cas" } } - ], - - } - "left": { - "type": "ipvm/wasm", // or make this a label for the microkernel? - "kernel: "Qm12345", // Or here? - "with": leftWasm, - "inputs": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "read-dns" } } - { "z": "QmFooBar" }, - ], - "outputs": ["a", "b"] - }, - "right": { - "type": "ipvm/wasm", - "with": "rightWasm", - "inputs": [ - { "foo": { "from": "read-dns/out" } }, - { "bar": "bafy123" } - ], - "outputs": ["a", "b"] - }, - "end": { - "type": "ipvm/wasm", - "with": "QmEndWasm", - "inputs": [ - { "a": { "from": "left" } }, - { "b": { "from": "right" } }, - { "c": 123 } - ] - }, - "cas": { - "type": "ipvm/wasm", - "with": "cafyCasWasm", - "inputs": [ - { "initial": "read-dns" }, - { "latest": "check-dns" } - ] - } - }, - "exception": { - "format-message": { - type: "ipvm/wasm", - with: handlerWasm - } - }, - "signature": "abcdef" -} -``` diff --git a/job/job.dot b/job/job.dot deleted file mode 100644 index aadc04a..0000000 --- a/job/job.dot +++ /dev/null @@ -1,39 +0,0 @@ -digraph G { - Pipeline -> {Job, JobL, JobR, JobE} - - // - - Job [shape = box] - Job -> {Invocation, Config, Version, requestorDid} - - Invocation [shape = box] - Invocation -> Wasm [label = "exec"] - Invocation -> {a, b, c} [label = "arg"] - - // - - JobL [shape = box] - JobL -> {InvocationL, ConfigL, VersionL, requestorDidL} - - InvocationL [shape = box] - InvocationL -> WasmL [label = "exec"] - InvocationL -> {d, e, f} [label = "arg"] - - // - - JobR [shape = box] - JobR -> {InvocationR, ConfigR, VersionR, requestorDidR} - - InvocationR [shape = box] - InvocationR -> WasmR [label = "exec"] - InvocationR -> {g, h, i} [label = "arg"] - - // - - JobE [shape = box] - JobE -> {InvocationE, ConfigE, VersionE, requestorDidE} - - InvocationE [shape = box] - InvocationE -> WasmR [label = "exec"] - InvocationE -> {j, k, l} [label = "arg"] -} diff --git a/job/job.json b/job/job.json deleted file mode 100644 index 925ca30..0000000 --- a/job/job.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "type": "ipvm/job", - "version": "0.1.0", - "requestor": "did:key:zAlice", - "nonce": "xjd72gs_k", - "ipvm/effects": { - "read-dns": { - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read" - }, - "check-dns": { - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/read", - "await": ["end"] - }, - "write-dns": { - "with": "dns://_dnslink.example.com?TYPE=TXT", - "do": "crud/update", - "inputs": [ - { "value": { "from": "end" } }, - ], - "await": ["cas"] - } - }, - "ipvm/wasm": { - "left": { - "with": "QmLeftWasm", - "inputs": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "effects/read-dns" } } - { "z": "QmFooBar" }, - ], - "outputs": ["a", "b"] - }, - "right": { - "with": "bafyRightWasm", - "inputs": [ - { "foo": { "from": "effects/read-dns" } }, - { "bar": "bafy123" } - ], - "outputs": ["a", "b"] - }, - "end": { - "with": "QmEndWasm", - "inputs": [ - { "a": { "from": "left" } }, - { "b": { "from": "right" } }, - { "c": 123 } - ] - }, - "cas": { - "with": "bafyCasWasm", - "inputs": [ - { "initial": "effects/read-dns" }, - { "latest": "effects/check-dns" } - ] - } - }, - "ipvm/exception": { - "format-message": { - "type": "ipvm/wasm", - "with": "bafyHandlerWasm" - } - }, - "signature": "abcdef" -} diff --git a/job/job.var1.json b/job/job.var1.json deleted file mode 100644 index cc915f0..0000000 --- a/job/job.var1.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "type": "ipvm/workflow", - "version": "0.0.1", - "requestor": "did:key:zAlice", - "nonce": "ABCDEF", - "config": { - "label": "fission/run_the_reports", - "start": "asap", - "timeoutAt": 1667878395, - "maxGas": 4096, - "authz": ["QmUcan1", "QmUcan2"], // TODO move to post-negotiated? - "visibility": "public", - "verification": { - "method": "ipvm/optimistic-zk", - "min": 1, - "replication": 2, - "referee": "ipns://abcdefghi" - } - }, - "jobs": { - "database": { - "type": "effect", - "with": "dnslink://example.com", - "do": "dnslink/resolve", - "timeout": "30s" - }, - "kickoff": { - "type": "wasm/1.0", - "with": "bafyWasm", - "input": [ - { "w": "Qm123456" }, - { "x": "Qmabcdef" }, - { "y": { "from": "database", "out": 0 } } // "Let the dataflow through you" - { "z": "QmFooBar" }, - ], - "affinities": [], // TODO lives here, or in - "always": ["bell"] - }, - "bell": { - "effect": "stdlib/system/bell", - "do": "bell/ring" - }, - "afterKickoff": { - "run": "system/bell", - "when": {"after": "kickoff", "scenario": "always"} - "input": "kickoff/_" - }, - "left": { - "sub1": { - "wasm": "bafyWasmLeftSub1", - "input": ["bafySomeArg1"] - }, - "sub2": { - "wasm": "bafyWasmLeftSub2", - "input": ["bafySomeArg2"] - } - }, - "subleft": { - "wasm": "bafyWasmSubLeft", - "input": [101, {"from": "left/sub1"}, 42] - }, - "right": { - "wasm": "bafyWasmRight", - "input": [{"from": "start", "output": 4}, {"from": "database"}] - }, - "end": { - "wasm": "bafyWasmEnd", - "input": [ - {"foo": "jobs/left/subleft/"} - ] - } - } - "signature": "abcdef" -} - -{ - "type": "ipvm/deal", - "version": "0.0.1", - "accepter": "", - "job": "Qmabcdef" -} - -{ - "type": "ipvm/invocation", - "version": "0.0.1", - "wasm": "Qm12345", - "inputs": ["Qmabcdef", "bafyghijk"] -} diff --git a/job/warpforge.json b/job/warpforge.json deleted file mode 100644 index 87f865a..0000000 --- a/job/warpforge.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "ipvm/job", - "version": "0.1.0", - "requestor": "did:key:zAlice", - "nonce": "xjd72gs_k", - "run": { - "container": { - "type": "ipvm/effect", - "with": "bafyWarpForge", - "do": "container/build", - "inputs": { - "rootfs": "abc", - "gawk": "def", - "data": "ghi" - } - }, - "awk-test": { - "type": "ipvm/warpforge", - "with": "container/out", - "do": "script/interpret", - "inputs": { - "interpreter": "bin/sh", - "contents": [ - "mkdir /out", - "/tools ..." - ] - }, - "outputs": { - "out": { - "from": "/out", - "packtype": "tar" - } - } - } - } -} diff --git a/workflow/README.md b/workflow/README.md index 352fa92..d61dff1 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -39,6 +39,37 @@ While IPVM in aggregate is capable of executing arbitrary programs, individual I These constraints impose specific practices. There is no first-class concept of persistent objects or loops. Loops, actors, vats, concurrent objects, and so on MAY be implemented on top of IPVM Workflows by enqueuing new workflows with the effect system (much like a [mailbox receive loop](https://www.erlang.org/doc/efficiency_guide/processes.html)). +## 1.2 Foundational Authority + +IPVM workflows are built on top of cryptographic capabilities, providing a strong basis for distributed computation in trustless networks. This even provides a clear basis for crossing Web 2.0 and Web3 systems, other computation networks, local operation (in full or part). + +The IPVM Workflow spec extends on several other specs that have been developed to provide this basis: + +``` +┌───────────────────────────────────────────────┬───────────────────────────┐ +│ │ │ +│ Human Configuration: │ │ +│ Defaults, Exception Handling, Comments, Tags │ │ +│ (IPVM Workflow) │ │ +│ │ Multi-Request Pipelining │ +├───────────────────────────────────────────────┤ (UCAN Invocation) │ +│ │ │ +│ IPVM Config, Verification Level, etc │ │ +│ (IPVM Task) │ │ +│ │ │ +├───────────────────────────────────────────────┴───────────────────────────┤ +│ │ +│ Call Graph │ +│ (UCAN Invocation) │ +│ │ +├───────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Authority │ +│ (UCAN Core) │ +│ │ +└───────────────────────────────────────────────────────────────────────────┘ +``` + # 2 Envelope The outer wrapper of a workflow MUST contain the following fields: @@ -178,7 +209,7 @@ The IPVM configuration struct defines secrecy, quotas, and verification strategy | Field | Type | Description | Required | Default | |----------|-------------------|-----------------------------------------|----------|--------------------------| | `secret` | `Boolean or null` | Whether the output is unsafe to publish | No | `null` | -| `check` | `Verification` | [Verification strategy](FIXME) | No | `"attestation"` | +| `check` | `Verification` | Verification strategy | No | `"attestation"` | | `time` | `TimeInterval` | Timeout | No | `[5, "minutes"]` | | `memory` | `InfoSize` | Memory limit | No | `[100, "kilo", "bytes"]` | | `disk` | `InfoSize` | Disk limit | No | `[10, "mega", "bytes"]` |

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�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 literal 111032 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 pMzdxtsK(~#>wtRw7oPTsM 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