Semantik eksekusi, batasan, dan keamanan

Halaman ini merangkum bagaimana Script berperilaku saat runtime (urutan, transaksi, error, penguncian), batasan statis apa yang dikenakan saat disimpan, dan model keamanannya. Untuk sintaksis, lihat Katalog Statement dan Ekspresi nilai; untuk kombinasi praktis, lihat Cookbook.

Urutan eksekusi

  • statements dieksekusi secara berurutan dari atas ke bawah. Ketika mencapai Return, eksekusi berhenti di titik tersebut.
  • Eksekusi berlangsung secara inline, pada jalur yang menangani permintaan panggilan. Respons panggilan itu sendiri adalah hasil eksekusi (untuk bentuk responsnya, lihat Permintaan dan respons di Ikhtisar Script), dan waktu yang diberikan untuk satu eksekusi dibahas di Anggaran waktu di bawah.

Semantik eksekusi

Guard (prakondisi)

Tidak ada statement guard khusus. Anda menyatakannya dengan If dan then:[Return]. Ketika kondisi dilanggar, ia mengembalikan hasil dan tidak menjalankan statement selanjutnya (tentu saja Script tanpa guard juga dimungkinkan).

{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
  "then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }

Tanpa transaksi dan kompensasi best-effort

Script bukanlah transaksi. Saat gagal, engine mencoba mengompensasi (compensation) pekerjaan yang telah dilakukan sejauh ini dan mengembalikan penyebab error, tetapi dengan keterbatasan berikut (diterima sebagai konsekuensi desain).

  • Membatalkan penghapusan membuat sys.id baru, sehingga referensi yang tadinya menunjuk ke sana menjadi rusak.
  • Efek eksternal (Http) bersifat ireversibel (panggilan yang sudah keluar beserta biayanya tidak dapat dibatalkan).
  • Kompensasi bisa sama sekali tidak berjalan, sehingga keadaan yang belum terkompensasi dapat tertinggal.

Jika Anda membutuhkan atomisitas sejati, tulis sendiri kompensasi di dalam Script tersebut, atau tempatkan operasi yang tidak dapat dibatalkan (misalnya panggilan eksternal) di paling akhir. Urutan yang paling berbahaya adalah yang "berhasil dirantai tetapi tidak dapat di-rollback, namun tampak aman."

Penguncian optimis

Persaingan update/patch dipersempit dengan version pada ResourceUpdate dan ResourcePatch. Jika Anda memberikan version (ekspresi nilai, Int), pembaruan dilakukan hanya jika cocok dengan sys.version target saat ini; ketidakcocokan di-abort karena error konflik versi (dapat ditangani secara lokal dengan Try/catch). Jika dihilangkan, berlaku last-write-wins tanpa pemeriksaan. Biasanya Anda membaca terlebih dahulu dengan ResourceRead atau ResourceFind lalu meneruskan sys.version tersebut (lihat CAS penguncian optimis di Cookbook).

Penulisan berbasis origin

Penulisan selalu tercermin di origin (draft), dan apakah diekspos ke delivery (CDA/ACDA) dikendalikan oleh publish (publish pada ResourceCreate/ResourceUpdate/ResourcePatch, atau ResourcePublish/ResourceUnpublish).

Apa yang dianggap sebagai kegagalan

  • Kegagalan sejati adalah error runtime statement: status akhir Http 400 atau lebih (4xx·5xx; bukan kegagalan jika ignoreStatusCode: true) atau timeout, body respons melebihi 10MiB, atau operasi resource yang gagal (target tidak ada, konflik versi, operasi yang tidak didukung, dsb.). Untuk kegagalan semacam ini engine akan meng-abort dan mengompensasi, dan Anda dapat menanganinya secara lokal dengan Try/catch/finally.
  • Return bukanlah error, melainkan penghentian dini yang normal. Ia bukan target catch (tidak ada konsep user-throw).
  • Di dalam catch, Anda merujuk { message } melalui /error. Statement mana yang gagal tidak disertakan di dalamnya.

Agregasi sisi server hanya untuk jumlah

Jumlahnya dihitung di sisi server oleh ResourceCount. Karena ia tidak membaca itemnya, ia tidak terikat pada batas atas jumlah item yang diproses.

Untuk sum dan group-by tidak ada operasi server khusus. Agregasi semacam itu harus Anda hitung sendiri dengan menelusuri ResourceForEach lalu menggunakan SetVar dan JsonLogic, sehingga terikat pada batas atas jumlah item yang diproses (tidak cocok untuk mengagregasi jutaan record). Jika Anda hanya membutuhkan jumlahnya, gunakan ResourceCount alih-alih menelusuri.

Tanpa penungguan atau penundaan

Script tidak memiliki statement Delay. Script dieksekusi sekali lalu selesai, dan tidak menunggu atau melakukan polling secara internal hingga job eksternal selesai.

Batasan statis (divalidasi saat disimpan)

Berikut ini diperiksa pada saat Script disimpan (pembuatan/perubahan). Jika dilanggar, penyimpanan ditolak (gagal saat penyusunan, bukan saat runtime). Pelanggaran mana yang ditolak dengan kode mana dibahas di Error.

BatasanNilai
Panggilan eksternal maksimum per definisi (Http·EmailSend)Per paket (lihat Paket Harga)
Total item yang diproses ResourceForEach maksimum (jika limit yang dideklarasikan tidak ada, menelusuri hingga nilai ini; gagal jika tercapai sementara masih ada kecocokan)10.000
SetVar maksimum per definisi (termasuk yang bersarang)10
Cache maksimum per definisi (termasuk yang bersarang, dijumlahkan tanpa memandang operasinya)5. Jika melebihi, penyimpanan ditolak
Cache di dalam blok Loop·ResourceForEachPenyimpanan ditolak
Cache.keyHanya literal, maksimal 128 karakter. Jika berupa ekspresi nilai, penyimpanan ditolak
Cache.ttlAntara 1 dan 30 detik; jika dihilangkan, 5 detik. Jika di luar rentang itu, penyimpanan ditolak
Total statement maksimum per definisi (termasuk yang bersarang)Per paket (lihat Paket Harga)
Batas atas Http.retry2
Panjang Regex.pattern128 karakter
Statement yang mengubah ServiceUserPenyimpanan ditolak. Hanya tiga statement pembacaan yang menerima resource ini
Jika anonymousCallEnabled bernilai true, createdBy: ":self" pada wherePenyimpanan ditolak

Batas tetap pada tabel di atas adalah nilai yang ditetapkan platform, sehingga sama tanpa memandang paket. Sebaliknya, jumlah total statement dan jumlah panggilan eksternal per definisi adalah batas per paket. Karena keduanya bukan error validasi melainkan batas paket, jika terlampaui, penyimpanan/pengubahan ditolak karena melampaui batas paket (definisi yang sama diperbolehkan pada paket yang lebih tinggi) dan dibuka dengan peningkatan paket. Angka per paket ada di Paket Harga.

Berbeda dengan panggilan eksternal seperti Http·EmailSend, penyerapan file Media tidak dihitung terhadap batas panggilan eksternal per definisi.

ResourceForEach adalah statement komposit yang memiliki anak, sehingga statement itu sendiri tidak dihitung ke jumlah panggilan eksternal. Statement panggilan eksternal (Http·EmailSend) di dalam onEach-lah yang dihitung (secara statis dihitung sebagai 1, tetapi benar-benar dijalankan untuk tiap item selama penelusuran). onEach dapat memuat panggilan eksternal atau penyerapan file Media, dan hal ini juga berlaku untuk body Loop. Berapa kali perulangan itu benar-benar berputar tidak masuk ke hitungan ini, melainkan masuk sebagai perkalian pada Anggaran waktu di bawah.

Batas atas panjang nilai (runtime)

Statement tanda tangan dan pemrosesan teks, serta Cache, memiliki batas atas untuk ukuran nilai yang ditanganinya. Yang dibatasi bukan panjang ekspresinya, melainkan panjang nilai hasil resolve dari ekspresi itu (enam belas karakter { /rawPayload } menunjuk ke puluhan KB), sehingga pemeriksaannya terjadi selama eksekusi, bukan saat penyimpanan.

SasaranBatas atasJika terlampaui
value pada Signature65.536 karakterStatement itu gagal (status 422)
value pada Hash128 karakterStatement itu gagal (status 422)
value pada Regex10.240 karakter (10KiB)Statement itu gagal (status 400)
value pada Cache10.240 byte (10KiB)Statement itu gagal (status 422)
  • Keempatnya sama seperti kegagalan runtime lainnya sehingga dapat ditangani secara lokal dengan Try/catch.
  • Batas atas Signature disesuaikan dengan ukuran body yang benar-benar dikirim penyedia (peristiwa pembayaran berukuran beberapa KB, dan webhook pesanan mencapai puluhan KB). Hash menempati posisi yang menyambung beberapa field saja, sehingga jauh lebih sempit.
  • Angka 128 karakter untuk Regex.pattern adalah pemeriksaan saat penyimpanan pada Batasan statis di atas. Panjang itu bukan perangkat untuk mencegah ledakan komputasi ((a+)+$ sudah berbahaya hanya dengan enam karakter). Yang mencegah ledakan adalah aturan yang mengharuskan pattern ditulis sebagai literal serta anggaran waktu di bawah; yang dijanjikan panjang itu hanyalah ukuran yang masih dapat dibaca dan ditinjau manusia.

Anggaran waktu (runtime)

Waktu yang diberikan untuk satu eksekusi ditentukan oleh satu rumus: min(30 detik + jumlah waktu yang dideklarasikan statement, 180 detik).

  • Anggarannya dihitung dari Script itu sendiri. Yang ditambahkan ke anggaran dasar hanyalah waktu yang dideklarasikan oleh definisi. Satu-satunya waktu terdeklarasi adalah timeoutMs pada Http dan EmailSend. Http memakai timeoutMs miliknya lagi pada setiap percobaan ulang, sehingga dihitung sebagai timeoutMs × (1 + retry), sedangkan EmailSend tidak melakukan percobaan ulang sehingga dihitung satu kali. Jika timeoutMs tidak ditulis, yang dihitung adalah nilai bawaannya (Http 30 detik, EmailSend 10 detik).
  • Pekerjaan tanpa waktu terdeklarasi diambil dari anggaran dasar 30 detik. Pembacaan dan penulisan resource, penyerapan file Media, serta pekerjaan yang dilakukan perulangan di dalamnya termasuk di sini. Karena itu anggaran dasar bukan angka formalitas, melainkan jatah yang sesungguhnya.
  • Cara menjumlahkannya mengikuti struktur statement. Statement yang diletakkan berurutan dijumlahkan, If mengambil yang lebih besar di antara dua cabangnya, dan Parallel mengambil yang terbesar di antara cabang-cabangnya. Loop mengalikan body dengan jumlah iterasi (maxIterations, 10.000 jika tidak dideklarasikan), dan ResourceForEach mengalikan onEach dengan jumlah item yang diproses (limit, 10.000 jika tidak dideklarasikan).
  • Perulangan tanpa panggilan eksternal memiliki waktu terdeklarasi 0. Karena itu anggaran dasar 30 detik menjadi batas yang sesungguhnya, dan di titik inilah Script yang memuat perulangan benar-benar terhenti.
  • Batas atas 180 detik tidak menghalangi penyimpanan, melainkan memotong eksekusi. Meskipun hasil perhitungannya melampaui batas atas, Script itu tetap tersimpan dan tetap berjalan, lalu berhenti begitu mencapai 180 detik.

Batas jumlah per paket

Untuk Script, jumlah per Organization dibatasi oleh paket.

PaketJumlah Script
Free10
Basic30
Pro100
EnterpriseTak terbatas

Terpisah dari itu, jumlah statement dan jumlah panggilan eksternal (Http·EmailSend) yang dapat dimuat oleh satu definisi Script juga dibatasi oleh paket. Saat menyimpan/mengubah definisi, jika melampaui batas paket tersebut, permintaan ditolak; untuk angka konkretnya, lihat Paket Harga.

Ketika batas tercapai, pembuatan Script baru ditolak.

Model keamanan

Header secret

Item pada Http.headers yang memiliki secret:true bersifat khusus CMA (administrator): nilainya tidak diekspos ke end-user (ServiceUser) dan hanya didekripsi tepat sebelum dikirim. Simpan rahasia seperti kunci API LLM di sini (bahkan saat dipaketkan menjadi App Bundle, nilai secret tetap disamarkan dan tidak pernah keluar dari Space asalnya).

secret pada Signature tidak mendapat perlakuan ini di dalam Space. Nilainya tidak dienkripsi dan disimpan persis seperti yang Anda tulis di definisi, sehingga role yang dapat membaca Script itu dapat melihat nilainya. Anggota (ServiceUser) tidak dapat membaca definisi Script (pembacaan dan penyusunannya khusus CMA, dan ACMA tidak memiliki Script API). Untuk Script yang menyimpan kunci verifikasi, lebih aman mempersempit role yang dapat membacanya.

Ketika keluar dari Space, ceritanya lain. Saat Script itu dipaketkan menjadi App Bundle, secret pada Signature disamarkan dan tidak keluar dari Space asalnya. Pada Http.headers, yang disamarkan adalah item yang membawa flag secret beserta header Authorization, sedangkan pada Signature, secret disamarkan tanpa syarat apa pun karena field itu sendiri merupakan kunci tanda tangan. Signature yang tersarang di dalam If·Loop·Try juga ikut disamarkan.

Identitas eksekusi dan otorisasi

  • Identitas eksekusi: selama eksekusi, setiap operasi resource dilakukan dengan identitas pengguna yang memanggil /execute. createdBy/updatedBy dari resource yang dibuat atau diubah adalah pemanggil, dan cakupan createdBy: ":self" juga diuraikan berdasarkan pemanggil. Pengecualiannya adalah panggilan anonim. Eksekusi yang masuk melalui /execute/anonymous tidak memiliki pemanggil, sehingga keduanya diuraikan berdasarkan penulis (Panggilan anonim).
  • Ada dua batas otorisasi, dan saat runtime izin resource tidak diperiksa ulang pada setiap statement.
    1. Saat penyusunan (penyimpanan): ketika sebuah Script disimpan, sistem memeriksa apakah penulis benar-benar memiliki izin resource dan aksi yang dipakai oleh statement-statement di dalamnya. Jika satu saja tidak ada, penyimpanan ditolak. Dengan kata lain, Script yang memuat operasi tanpa izin memang tidak akan pernah tersimpan sejak awal. Statement yang memilih resource, baik berupa leaf maupun ResourceForEach yang memiliki blok, semuanya menjalani pemeriksaan ini. Definisi yang sudah tersimpan pun diperiksa ulang ketika diubah, sehingga setelah izinnya dicabut, definisi itu tidak dapat diperbaiki dan disimpan lagi.
      • Direktori anggota (ServiceUser) diperiksa bukan lewat map izin, melainkan lewat sumbu pengaturan. Untuk memakai resource: "ServiceUser" pada tiga statement pembacaan, settings pada SpaceRole penulis harus memuat SETTING_SERVICE_LOGIN (atau SETTING_ALL) (settings pada SpaceRole). Alasannya, direktori anggota adalah resource yang dikelola pengaturan Space di semua jalur lainnya juga.
      • Statement yang mengubah anggota tidak dapat disimpan dengan role apa pun. Karena di Script memang tidak ada jalan untuk membuat, mengubah, atau menghapus anggota, penolakannya bukan karena izin kurang (403) melainkan karena statement yang salah tulis (400). Artinya, ini bukan celah yang bisa ditutup dengan menambah izin.
    2. Saat pemanggilan (/execute): hanya izin Execute Script milik pemanggil yang diperiksa. Tanpa izin itu, hasilnya 403. Jika lolos, izin resource per statement tidak diperiksa lagi saat runtime; eksekusi langsung berjalan. Cara kerjanya seperti izin eksekusi fungsi dalam pemrograman. Jika Anda memiliki izin untuk menjalankan fungsi tersebut, izin untuk setiap operasi individual di dalamnya tidak ditanyakan lagi. Pada jalur panggilan anonim, pemeriksaan ini tidak ada. Sebab tidak ada pemanggil yang bisa diperiksa, sehingga membuka jalur itu setara dengan mempublikasikan satu Script tanpa autentikasi.
  • Pemblokiran pemanggilan langsung (directCallEnabled): jika directCallEnabled dari Script bernilai false, pemanggilan langsung /execute itu sendiri ditolak. Gerbang ini terkena setelah pemeriksaan izin Execute dilewati, sehingga pemanggilan tetap terblokir meskipun Anda memiliki izin Execute. Pemanggil yang tidak memiliki izin menerima 403 sebelum mencapai gerbang ini. Karena gerbang ini hanya ada pada endpoint tersebut, aksi terhubung (script) pada Webhook dan Scheduler tetap menjalankannya seperti biasa. Nilai bawaannya adalah true (memperbolehkan pemanggilan langsung).
  • Panggilan anonim (anonymousCallEnabled): nilai bawaannya adalah false. Jika disetel true, hanya Script itu yang juga dapat dijalankan melalui jalur khusus tanpa autentikasi (/execute/anonymous), dan saat itu identitas eksekusinya adalah penulis, bukan pemanggil. Dari kedua batas di atas, pemeriksaan saat pemanggilan (izin Execute) tidak ada pada jalur itu, sehingga autentikasi yang sebenarnya dilakukan oleh Script itu sendiri (verifikasi tanda tangan atas permintaan yang diterima). Syarat mengaktifkannya dan aturan penyimpanannya dibahas di Panggilan anonim.
  • Cakupan kepemilikan: createdBy: ":self" pada filter where berarti "hanya yang dibuat oleh pemanggil saat ini" (misalnya, hanya membaca dompet milik Anda sendiri). Pada Script yang mengizinkan panggilan anonim, filter ini tidak dapat dipakai. Karena tidak ada pemanggil, ia diuraikan menjadi penulis, sehingga makna aslinya sebagai cakupan kepemilikan tidak lagi berlaku.
  • Izin yang didelegasikan (perhatian bagi penulis): menggabungkan kedua batas di atas, menjalankan sebuah Script setara dengan bertindak dengan izin penulis yang didelegasikan kepadanya. Pemanggil hanya perlu Execute, dan statement di dalam Script berjalan persis dalam cakupan yang diotorisasikan kepada penulis saat disimpan. Akibatnya, operasi resource yang tidak dapat dilakukan sendiri oleh pemanggil pun tetap bisa terjadi melalui Script. Karena izin yang diberikan kepada penulis itulah jangkauan efektif dari Script tersebut, tentukan dengan cermat operasi apa yang Anda masukkan ke dalam Script.

Daftar periksa ringkas

Sebelum menyimpan, periksa hal-hal berikut.

  • Jika Anda mengaktifkan panggilan anonim (anonymousCallEnabled), tidak ada createdBy: ":self" pada where, dan statement yang memverifikasi permintaan yang diterima (Signature, dan sejenisnya) diletakkan di paling depan.
  • Jumlah panggilan eksternal (Http·EmailSend) dan total statement berada dalam batas paket, SetVar 10 atau kurang, dan Cache 5 atau kurang.
  • Jika memakai Cache, Anda menulis key sebagai literal dan tidak meletakkannya di dalam Loop atau ResourceForEach.
  • Jika menelusuri kumpulan besar dengan ResourceForEach, Anda telah mendeklarasikan limit atau memastikan ukurannya dapat diselesaikan seluruhnya.
  • Jika Anda memuat perulangan (Loop·ResourceForEach), Anda sudah memastikan bahwa perulangan itu masuk sebagai perkalian ke Anggaran waktu (tanpa panggilan eksternal, batasnya adalah anggaran dasar 30 detik).
  • Nilai secret hanya dimasukkan melalui secret:true pada Http.headers (karena Signature.secret tidak disimpan terenkripsi, Anda sudah memeriksa role yang dapat membaca Script tersebut).
  • Pesan yang dipakai untuk verifikasi tanda tangan diambil dengan { /rawPayload }, bukan /payload.
  • Jika ada statement yang membaca anggota (ServiceUser), penulis memiliki SETTING_SERVICE_LOGIN, dan Anda tidak menyertakan statement yang mengubah resource tersebut.
  • Operasi yang tidak dapat dibatalkan (panggilan eksternal) ditempatkan sebisa mungkin di bagian akhir.
  • Jika Anda mengkhawatirkan persaingan update/patch, gunakan version pada ResourceUpdate atau ResourcePatch.
  • Untuk mengembalikan hasil, Anda menentukan Return.value.

Error

Berikut adalah kode yang muncul saat penyimpanan ditolak karena bentuk definisi melanggar batasan statis. Kode yang melanggar aturan ekspresi nilai ada di Error pada Ekspresi nilai, sedangkan kode yang muncul saat pemanggilan dan penghapusan ada di Error pada endpoint. Untuk kode yang berlaku umum pada semua resource, lihat Error umum.

KodeKondisi
WGL400066Jumlah statement Cache dalam satu definisi melebihi 5.
WGL400068Anda menempatkan statement Cache di dalam blok Loop atau ResourceForEach.
WGL400067Anda menulis referensi { /pointer } pada key statement Cache, bukan literal.
WGL400065ttl pada statement Cache berada di luar rentang yang diizinkan.
WGL400063Anda menulis field yang tidak sesuai dengan action pada statement Cache (ttl pada Get, defaultValue pada Set).
WGL400060Anda menulis "ServiceUser" pada resource milik statement penulisan (ResourceCreate·ResourceUpdate·ResourcePatch·ResourceDelete serta statement publikasi dan pengarsipan).
WGL400061Anda menulis createdBy: ":self" pada where statement pembacaan di dalam Script yang mengizinkan panggilan anonim (anonymousCallEnabled).
WGL400023Jumlah statement SetVar dalam satu definisi melebihi 10.
WGL400026retry pada statement Http melebihi batas atas 2.
WGL400036ResourceForEach melebihi batas atas jumlah item yang dapat diproses.
WGL429005Total jumlah statement dalam satu definisi melebihi batas paket.
WGL429006Jumlah panggilan eksternal (Http·EmailSend) dalam satu definisi melebihi batas paket.
WGL403015Penulis tidak memiliki izin atas resource dan aksi yang ditangani statement di dalam definisi itu. Meskipun izin itu ada, permintaan tetap ditolak bila izin tersebut disertai filter contentType, createdBy, atau tag. Izin itu harus berupa izin tanpa syarat. Satu-satunya pengecualian adalah Create pada Content: untuk aksi itu izin yang dibatasi cakupan contentType pun diterima, dan cakupan itu dibandingkan dengan contentType yang ditulis pada statement tersebut (Create pada Media tidak memiliki pengecualian ini). Kasus ketika resource pada statement pembacaan diisi "ServiceUser" sedangkan penulis tidak memiliki SETTING_SERVICE_LOGIN juga memakai kode ini.