실행 시맨틱, 제약, 보안
Script가 런타임에 어떻게 동작하는지(순서, 트랜잭션, 에러, 잠금), 저장 시 어떤 정적 제약을 받는지, 그리고 보안 모델을 정리합니다. 문법은 Statement 카탈로그와 값 표현식, 실전 조합은 쿡북을 참조하세요.
실행 순서
statements는 위에서 아래로 순차 실행됩니다.Return에 도달하면 그 지점에서 종료합니다.- 실행은 호출 요청을 처리하는 경로에서 인라인으로 이루어집니다. 호출의 응답이 곧 실행 결과이고(응답 형태는 Script 개요의 요청과 응답 참조), 한 번의 실행에 주어지는 시간은 아래 시간 예산에서 다룹니다.
실행 시맨틱
Guard (사전 조건)
전용 guard Statement는 없습니다. If와 then:[Return]으로 표현합니다. 조건 위반 시 결과를 반환하고 이후 Statement는 실행하지 않습니다(guard가 없는 Script도 당연히 가능합니다).
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }트랜잭션 없음과 best-effort 보상
Script는 트랜잭션이 아닙니다. 실패 시 엔진이 지금까지 한 작업의 보상(compensation)을 시도하고 에러 원인을 리턴하지만, 다음 한계가 있습니다(설계상 감수한 것입니다).
- 삭제를 되돌리면 새
sys.id가 만들어지므로, 이를 가리키던 참조가 깨집니다. - 외부 효과(
Http)는 비가역입니다(이미 나간 호출과 과금은 되돌릴 수 없습니다). - 보상이 아예 실행되지 않아 미보상 상태가 남을 수 있습니다.
진짜 원자성이 필요하면 사용자가 Script로 직접 보상하거나, 되돌릴 수 없는 작업(외부 호출 등)을 맨 마지막에 배치합니다. "연쇄는 되는데 롤백은 안 되면서 안전해 보이는" 순서가 가장 위험합니다.
낙관적 잠금
update/patch 경합은 ResourceUpdate와 ResourcePatch의 version으로 좁힙니다. version(값표현식, Int)을 주면 대상의 현재 sys.version과 일치할 때만 갱신하고, 불일치는 버전 충돌 에러로 abort됩니다(Try/catch로 국소 처리 가능). 생략하면 검사 없이 last-write-wins입니다. 보통 ResourceRead나 ResourceFind로 먼저 읽어 그 sys.version을 넘깁니다(쿡북의 낙관적 잠금 CAS 참조).
origin 기준 쓰기
쓰기는 항상 origin(draft)에 반영되고, delivery(CDA/ACDA) 노출은 publish로 제어합니다(ResourceCreate/ResourceUpdate/ResourcePatch의 publish, 또는 ResourcePublish/ResourceUnpublish).
무엇이 실패인가
실행 도중 Statement 하나가 실패하면, 그 실패를 처리하는 Try가 없는 한 실행은 그 자리에서 중단됩니다. 이때 엔진은 그때까지 수행한 작업을 되돌리려고 시도합니다. 되돌리기에는 한계가 있으며, 자세한 내용은 트랜잭션 없음과 best-effort 보상에서 다룹니다.
실패한 Statement가 Try 안에 있으면 그 실패를 catch에서 처리할 수 있습니다. catch 안에서는 /error로 실패 내용({ message })을 참조합니다. 어느 Statement에서 실패했는지는 담기지 않습니다.
다음 경우가 실패로 처리됩니다.
- 리소스 작업이 실패한 경우(대상이 없거나, 버전이 맞지 않거나, 지원하지 않는 연산인 경우 등)
Http가 실패한 경우(Http가 실패하는 두 방식 참조)- Media 파일 변환이 실패한 경우(파일 변환이 실패하는 경우 참조)
- Statement마다 정해진 런타임 상한을 넘는 경우(예: 값 길이 상한)
Return은 실패가 아닙니다. 실행을 정상적으로 일찍 끝내는 것이므로 catch로 넘어가지 않습니다.
Http가 실패하는 두 방식
Http의 실패는 status 응답으로 처리되는지에 따라 두 가지로 나뉩니다. 이 차이에 따라 retry와 ignoreStatusCode가 적용되는지가 달라집니다.
| 구분 | status 응답으로 처리되는 경우 | 곧바로 실패하는 경우 |
|---|---|---|
| 해당하는 경우 | 대상 API가 400 이상의 status를 응답한 경우, 시간이 초과된 경우(status 504), 대상 API에 연결하지 못했거나 응답을 읽지 못한 경우(status 502), 응답 본문이 10MiB를 넘는 경우(status 502) | responseType이 "Json"인데 성공 응답의 본문이 JSON이 아닌 경우, 보낼 파일이 다운로드 한도를 넘는 경우, 파일 URL이 2xx가 아닌 status를 응답하거나 파일 URL에 연결할 수 없는 경우, part 값의 형식이 잘못된 경우 |
retry | 지정한 횟수만큼 재시도합니다 | 재시도하지 않습니다 |
ignoreStatusCode: true | 실패로 보지 않고 { status, body }를 결과로 바인딩합니다 | 적용되지 않으며 실패로 처리됩니다 |
Try/catch | catch에서 처리할 수 있습니다 | catch에서 처리할 수 있습니다 |
파일 전송 과정에서 실패하는 조건은 파일을 본문으로 보내기에서 자세히 다룹니다.
서버측 집계는 건수만
건수는 ResourceCount가 서버에서 셉니다. 항목을 읽어 오지 않으므로 처리 항목 수 상한을 받지 않습니다.
sum과 group-by는 전용 서버 작업이 없습니다. 그런 집계는 ResourceForEach로 순회하면서 SetVar와 JsonLogic으로 직접 계산해야 하고, 따라서 처리 항목 수 상한에 묶입니다(수백만 건 집계엔 부적합). 건수만 필요하면 순회하지 말고 ResourceCount를 씁니다.
대기와 지연 없음
Script에는 Delay Statement가 없습니다. Script는 한 번 실행되고 끝이며, 외부 job이 끝날 때까지 내부에서 대기하거나 폴링하지 않습니다.
정적 제약 (저장 시 검증)
아래는 Script를 저장(생성/수정)하는 시점에 검사됩니다. 위반하면 저장이 거부됩니다(런타임이 아니라 저작 시 실패). 어떤 위반이 어떤 코드로 거부되는지는 오류에서 다룹니다.
| 제약 | 값 |
|---|---|
한 정의당 외부 호출 최대(Http·EmailSend) | 플랜별(요금제 참조) |
ResourceForEach 총 처리 항목 최대(선언한 limit이 없으면 이 값까지 순회, 매치가 남은 채 걸리면 실패) | 10,000 |
한 정의당 SetVar 최대(중첩 포함) | 10 |
한 정의당 Cache 최대(중첩 포함, 연산과 무관하게 합산) | 5. 넘으면 저장 거부 |
Loop·ResourceForEach 블록 안의 Cache | 저장 거부 |
Cache.key | 리터럴만, 최대 128자. 값 표현식이면 저장 거부 |
Cache.ttl | 1에서 30초 사이, 생략 시 5초. 벗어나면 저장 거부 |
| 한 정의당 전체 Statement 최대(중첩 포함) | 플랜별(요금제 참조) |
Http.retry 상한 | 2 |
한 Http의 요청 본문 필드(body·urlBody·multipartBody) | 셋 중 하나만. 둘 이상 적으면 저장 거부 |
Http.multipartBody의 part 수 | 1개 이상. 비어 있으면 저장 거부 |
multipartBody part의 value·url | 정확히 하나. 둘 다 적거나 둘 다 비우면 저장 거부 |
multipartBody part의 fileName | url을 적은 part에만. url 없이 적으면 저장 거부 |
Regex.pattern 길이 | 128자 |
한 정의당 secrets 최대 | 20 |
secrets의 key | 1~64자, 영문·숫자·_·-만. 같은 key가 둘이면 저장 거부 |
secrets의 value | 1~4,096자. 벗어나면 저장 거부 |
{ /secrets… } 참조 | 정확히 /secrets/<선언한 key>. 아니면 저장 거부 |
| ServiceUser를 만들거나 지우는 Statement | 저장 거부. 읽기 세 Statement와 ResourceUpdate·ResourcePatch만 이 리소스를 받습니다 |
anonymousCallEnabled가 true면 where의 createdBy: ":self" | 저장 거부 |
Media 인제스트 지시의 transformations(파일 변환) | 배열만. 원소의 type은 PdfWatermark, color는 #RRGGBB. text는 작성한 문자열 전체({ /pointer }의 경로 포함)가 쓸 수 있는 글자로 된 한 줄. 어기면 저장 거부 |
PdfWatermark의 리터럴 text 길이 | 1~64자. 벗어나면 저장 거부. { /pointer }가 든 text는 저장할 때 검사하지 않고, 실행할 때 해석된 값으로 검사 |
위 표의 고정 한도는 플랫폼이 정한 값이라 플랜과 무관하게 같습니다. 반면 한 정의당 전체 Statement 수와 외부 호출 수는 플랜별 한도입니다. 이 둘은 유효성 오류가 아니라 플랜 한도라서, 초과하면 저장·수정이 플랜 한도 초과로 거부되고(같은 정의도 상위 플랜에서는 허용됩니다) 업그레이드로 풀립니다. 플랜별 수치는 요금제에 있습니다.
Media 파일 인제스트는
Http·EmailSend같은 외부 호출과 달리 한 정의당 외부 호출 한도에 포함되지 않습니다.
ResourceForEach는 자식을 소유하는 복합 Statement라 그 자체는 외부 호출 수에 세지 않습니다.onEach안의 외부 호출 Statement(Http·EmailSend)가 세어집니다(정적으로는 1로 세지만 순회하며 항목마다 실제로 실행됩니다).onEach에는 외부 호출이나 Media 파일 인제스트를 담을 수 있으며, 이는Loop의 body도 마찬가지입니다. 반복이 실제로 몇 번 도는지는 이 카운트에 들어가지 않고, 대신 아래 시간 예산에서 곱셈으로 들어갑니다.
값 길이 상한 (런타임)
서명과 텍스트 처리 Statement, 그리고 Cache는 다루는 값의 크기에 상한이 있습니다. 표현식의 길이가 아니라 그 표현식이 resolve된 값의 길이이고({ /rawPayload } 열여섯 자가 수십 KB를 가리킵니다), 그래서 저장 시점이 아니라 실행 중에 검사됩니다.
- 넷 다 다른 런타임 실패와 같아서
Try/catch로 국소 처리할 수 있습니다. Signature의 상한은 실제 제공자가 보내는 본문 크기에 맞춘 것입니다(결제 이벤트는 수 KB, 주문 웹훅은 수십 KB에 이릅니다).Hash는 필드 몇 개를 이어 붙이는 자리라 훨씬 좁습니다.Regex.pattern의 128자는 위 정적 제약에 있는 저장 시 검사입니다. 이 길이는 폭주를 막는 장치가 아닙니다((a+)+$는 여섯 자로도 위험합니다). 폭주를 막는 것은 패턴을 리터럴로만 쓰게 한 규칙과 아래 시간 예산이고, 이 길이가 보장하는 것은 사람이 읽고 검토할 수 있는 크기뿐입니다.
시간 예산 (런타임)
실행 한 번에 허용되는 시간은 최대 180초입니다. 실제로 허용되는 시간은 Script 정의(파일을 보내는 경우에는 플랜도)에 따라 정해지며, 기본 30초에 외부 호출이 사용할 수 있는 시간을 더한 값입니다.
이 예산은 실행 전체에 적용됩니다. 이와 별도로 Http 요청 한 번은 그 요청의 timeoutMs 안에 끝나야 합니다.
기본 30초
리소스를 읽고 쓰는 작업이나 Media 파일을 받아 처리하는 작업처럼 외부 호출이 아닌 작업은 모두 이 30초 안에서 처리됩니다. 외부 호출이 없는 Script에는 30초가 허용됩니다.
Statement별로 더해지는 시간
순서대로 놓인 Statement의 시간은 모두 더합니다.
| Statement | 더해지는 시간 |
|---|---|
Http | timeoutMs(지정하지 않으면 30초). 파일을 보내면 파일마다 다운로드 시간(아래 표)을 더하고, retry를 지정했으면 전체에 시도 횟수(1 + retry)를 곱합니다 |
EmailSend | timeoutMs(지정하지 않으면 10초). 재시도하지 않으므로 한 번만 더합니다 |
If | then과 else 가운데 더 긴 쪽의 시간 |
Parallel | branches 가운데 가장 긴 것의 시간 |
Try | body, catch, finally의 시간을 모두 더한 값 |
Loop | body의 시간에 maxIterations(지정하지 않으면 10,000)를 곱한 값 |
ResourceForEach | onEach의 시간에 limit(지정하지 않으면 10,000)를 곱한 값 |
| 그 밖의 Statement(다른 Statement를 담지 않는 것) | 더하지 않습니다. 기본 30초 안에서 처리됩니다 |
반복 안에 외부 호출이 없으면 반복 횟수를 곱해도 더해지는 시간은 0입니다. 따라서 그런 반복은 기본 30초 안에 끝나야 합니다.
파일 하나당 다운로드 시간
| Plan | 다운로드 시간 |
|---|---|
| Free | 약 5.2초 |
| Basic | 약 10.5초 |
| Pro | 약 10.5초 |
| Enterprise | 약 107.4초 |
이 값은 플랜의 다운로드 한도만큼을 초당 10,000,000바이트로 받는 데 걸리는 시간입니다.
계산 예
Pro 플랜에서 사진 두 장을 보내는 Http(timeoutMs 30초, retry 1)를 하나 호출하는 Script는 다음과 같이 계산합니다.
Http: (30초 + 10.5초 × 2) × 2 = 약 102초- 실행 전체: 30초 + 102초 = 약 132초
132초는 180초보다 짧으므로 이 Script에는 132초가 허용됩니다.
180초를 넘는 경우
계산한 값이 180초를 넘어도 Script는 저장되고 실행됩니다. 다만 실행이 180초에 도달하면 그 시점에서 중단됩니다.
플랜별 개수 한도
Script는 Organization당 개수가 플랜별로 제한됩니다.
| Plan | Script 개수 |
|---|---|
| Free | 10 |
| Basic | 30 |
| Pro | 100 |
| Enterprise | 무제한 |
이와 별개로, 한 Script 정의가 담을 수 있는 Statement 수와 외부 호출(Http·EmailSend) 수도 플랜별로 제한됩니다. 정의를 저장·수정할 때 그 플랜의 한도를 넘으면 거부되며, 구체 수치는 요금제를 참조하세요.
한도에 도달하면 새 Script 생성이 거부됩니다.
Http 파일 다운로드 크기
Http가 요청 본문으로 보낼 파일을 URL에서 받아 올 때(urlBody, multipartBody의 url part), 파일 URL 하나당 받을 수 있는 크기가 플랜별로 제한됩니다. 그 Script를 소유한 Space가 속한 Organization의 플랜을 따릅니다.
| Plan | 파일 URL 하나당 최대 크기 |
|---|---|
| Free | 50MiB |
| Basic | 100MiB |
| Pro | 100MiB |
| Enterprise | 1GiB |
- 한도는 URL마다 따로 적용됩니다.
urlpart가 여럿이면 각 파일이 이 한도 안에 있어야 합니다. - 한도를 넘는 파일을 보내려고 하면 그
Http가 실패합니다(Http가 실패하는 두 방식 참조). - 이 한도는 시간 예산에 더해지는 다운로드 시간의 기준이기도 합니다.
보안 모델
secrets
외부 API 키, 결제 대행사(PG)의 시크릿 키, 웹훅 서명 키, 미리 계산한 Basic … 인증값 같은 비밀 값은 Statement에 값을 그대로 적지 않습니다. 정의의 secrets에 선언하고, Statement에서는 { /secrets/<key> }로 읽습니다. 공개된 테스트 키도 같은 자리에 둡니다. 그러면 실제 키로 바꿀 때 Statement는 그대로 두고 secrets의 값만 바꾸면 됩니다.
"secrets": [
{ "key": "LLM_API_KEY", "value": "sk-...", "description": "LLM 호출용 API 키" },
{ "key": "WEBHOOK_SIGNING_SECRET", "value": "whsec_...", "description": "" }
],
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.openai.com/v1/chat/completions",
"headers": [ { "key": "Authorization", "value": "Bearer { /secrets/LLM_API_KEY }" } ],
"body": { /* ... */ }, "name": "llm" }
]| 필드 | 필수 | 규칙 |
|---|---|---|
key | 필수 | 1~64자이고 영문·숫자·_·-만 씁니다. 한 Script 안에서 유일해야 합니다. |
value | 필수 | 1~4,096자 문자열입니다. 정적 값이라 템플릿으로 해석하지 않으므로, 안에 { /… }를 넣어도 글자 그대로 쓰입니다. |
description | 필수 | 용도 설명이며 0~128자입니다. 빈 문자열 ""도 됩니다. |
- 저장:
value는 WEEGLOO가 암호화해 안전하게 저장합니다. Statement는{ /secrets/<key> }로 원래 값을 읽습니다. - 조회: Script를 돌려주는 API 응답(조회·생성·수정)에는
value가 원래 값으로 반환됩니다. 그 Script를 읽을 수 있는 역할에게는 값이 보이므로 읽기 권한을 좁게 둡니다. 회원(ServiceUser)은 Script 정의를 읽을 수 없습니다(조회와 저작은 CMA 전용이고 ACMA에는 Script API가 없습니다). - 수정: 전체 수정(PUT)은 정의 전체를 교체합니다.
secrets를 빼고 보내면 선언해 둔 비밀 값이 지워지고, Statement가 아직 그 값을{ /secrets/… }로 참조하고 있으면 저장이 거부됩니다. 그래서 수정할 때마다secrets를 함께 보냅니다. 조회 응답의 값을 그대로 돌려보내면 됩니다. - 실행 중의 값: 읽은 값으로 무엇을 할지는 Statement가 정합니다. 읽은 값을
Return으로 돌려주면 실행 응답과 ScriptLog에 남고,SetVar·Cache·Content에 넣으면 그 자리에 원래 값으로 남습니다. 비밀 값은 그 값이 필요한 외부 API에만 보냅니다. - App Bundle: Script가 App Bundle로 패킹될 때
secrets의value는******로 가려지고key와description은 그대로 실립니다. 그 App Bundle을 설치해 만들어진 Script는 비밀 값이******인 상태이므로, 설치한 쪽에서 실제 값을 넣어야 그 값을 읽는 Statement가 동작합니다.secrets밖의 자리(Http의 헤더,Signature의secret등)에 적은 값은 가려지지 않고 그대로 실립니다.
한 Script에는 secrets를 20개까지 둘 수 있습니다. 포인터 규칙은 값 표현식의 /secrets에서 다룹니다.
실행 신원과 인가
- 실행 신원: 실행 중 모든 리소스 작업은
/execute를 호출한 사용자 신원으로 수행됩니다. 만들어지거나 수정되는 리소스의createdBy/updatedBy가 호출자이고,createdBy: ":self"스코프도 호출자를 기준으로 풀립니다. 예외는 익명 호출입니다./execute/anonymous로 들어온 실행에는 호출자가 없으므로 두 가지 모두 작성자를 기준으로 풀립니다(익명 호출). - 인가 경계는 둘이며, 런타임에는 Statement마다 리소스 권한을 재검사하지 않습니다.
- 저작 시(저장): Script를 저장할 때, 작성자가 그 Statement들이 쓰는 리소스와 액션 권한을 실제로 갖는지 검사합니다. 하나라도 없으면 저장이 거부됩니다. 즉, 권한 없는 작업이 담긴 Script는 애초에 저장되지 않습니다. 리소스를 고르는 Statement는 leaf든 블록을 소유하는
ResourceForEach든 모두 이 검사를 받습니다. 이미 저장해 둔 정의도 수정할 때 다시 검사되므로, 권한이 회수된 뒤에는 그 정의를 고쳐 저장할 수 없습니다.- 회원 디렉터리(ServiceUser)는 권한 맵이 아니라 설정 축으로 검사합니다. 읽기 세 Statement와
ResourceUpdate·ResourcePatch에resource: "ServiceUser"를 쓰려면 작성자의 SpaceRolesettings에SETTING_SERVICE_LOGIN(또는SETTING_ALL)이 있어야 합니다(SpaceRole의 settings). 회원 디렉터리가 다른 모든 경로에서도 Space 설정이 관장하는 리소스이기 때문입니다. - 회원을 만들거나 지우는 Statement는 어떤 역할로도 저장되지 않습니다. Script는 회원을 만들거나 지울 수 없으므로, 권한 부족(
403)이 아니라 잘못 쓴 Statement(400)로 거부됩니다. 권한을 더해 닫을 수 있는 공백이 아니라는 뜻입니다. 허용되는 변경은ResourceUpdate·ResourcePatch의roleOverride와enableLogin뿐입니다(회원 디렉터리 다루기).
- 회원 디렉터리(ServiceUser)는 권한 맵이 아니라 설정 축으로 검사합니다. 읽기 세 Statement와
- 호출 시(
/execute): 호출자의 Script Execute 권한만 검사합니다. 권한이 없으면403입니다. 통과하면 Statement별 리소스 권한은 런타임에 다시 확인하지 않고 실행합니다. 프로그래밍의 함수 실행 권한과 같은 방식입니다. 함수를 실행할 권한만 있으면 그 안의 개별 작업 권한은 다시 묻지 않습니다. 익명 호출 경로에는 이 검사가 없습니다. 검사할 호출자가 없기 때문이고, 그래서 그 경로를 여는 것은 Script 하나를 무인증으로 공개하는 일과 같습니다.
- 저작 시(저장): Script를 저장할 때, 작성자가 그 Statement들이 쓰는 리소스와 액션 권한을 실제로 갖는지 검사합니다. 하나라도 없으면 저장이 거부됩니다. 즉, 권한 없는 작업이 담긴 Script는 애초에 저장되지 않습니다. 리소스를 고르는 Statement는 leaf든 블록을 소유하는
- 직접 호출 차단 (
directCallEnabled): Script의directCallEnabled가false이면/execute직접 호출 자체가 거부됩니다. 이 게이트는 Execute 권한 검사를 통과한 뒤에 걸리므로, Execute 권한이 있어도 막힙니다. 권한이 없는 호출자는 이 게이트에 닿기 전에403을 받습니다. 이 게이트는 그 엔드포인트에만 있으므로, Webhook의 연결 액션(script)과 Scheduler는 그대로 실행합니다. 기본값은true(직접 호출 허용)입니다. - 익명 호출 (
anonymousCallEnabled): 기본값은false입니다.true로 두면 그 Script만 인증 없는 전용 경로(/execute/anonymous)로도 실행되고, 그때 실행 신원은 호출자가 아니라 작성자입니다. 위 두 경계 중 호출 시 검사(Execute 권한)가 그 경로에는 없으므로, 실질적인 인증은 Script가 스스로 합니다(받은 요청의 서명 검증). 켜는 조건과 저장 규칙은 익명 호출에서 다룹니다. - 소유권 스코프:
where필터의createdBy: ":self"는 "현재 호출자가 만든 것만"을 뜻합니다(예: 내 지갑만 조회). 익명 호출을 허용한 Script에서는 이 필터를 쓸 수 없습니다. 호출자가 없어 작성자로 풀리므로, 소유권 스코프라는 원래 뜻이 성립하지 않습니다. - 위임된 권한 (작성 주의): 위 두 경계를 합치면, Script 실행은 작성자의 권한을 위임받아 수행되는 것과 같습니다. 호출자는 Execute 하나만 있으면 되고, Script 안 Statement는 작성자가 저장 시 인가받은 범위에서 그대로 실행됩니다. 따라서 호출자가 스스로는 하지 못하는 리소스 작업도 Script를 통해 일어날 수 있습니다. 작성자에게 부여된 권한이 곧 그 Script의 영향 범위이므로, Script에 담는 동작은 신중히 정합니다.
요약 체크리스트
저장 전에 다음을 확인합니다.
- 익명 호출(
anonymousCallEnabled)을 켰다면where에createdBy: ":self"가 없고, 받은 요청을 검증하는 Statement(Signature등)를 맨 앞에 두었습니다. - 외부 호출(
Http·EmailSend) 수와 전체 Statement 수는 플랜 한도 이내이고,SetVar는 10개 이하,Cache는 5개 이하입니다. Cache를 썼다면key를 리터럴로 적었고Loop나ResourceForEach안에 두지 않았습니다.ResourceForEach로 큰 집합을 돌면limit을 선언하거나 완주 가능한 크기인지 확인했습니다.- 반복(
Loop·ResourceForEach)을 담았다면 그 반복이 곱셈으로 시간 예산에 들어가는 것을 확인했습니다(외부 호출이 없으면 30초 기본 예산이 한도입니다). - 외부 API 키나 서명 키 같은 비밀 값은
secrets에 선언하고{ /secrets/<key> }로 읽었습니다. Statement에 값을 그대로 적은 비밀 값은 없습니다. - 전체 수정(PUT)으로 저장한다면
secrets를 함께 보냈습니다. - 서명 검증에 쓰는 메시지는
/payload가 아니라{ /rawPayload }로 지정했습니다. - 회원(ServiceUser)을 다루는 Statement가 있으면 작성자에게
SETTING_SERVICE_LOGIN이 있고, 그 리소스를 만들거나 지우는 Statement는 넣지 않았습니다. - 되돌릴 수 없는 작업(외부 호출)은 되도록 뒤에 두었습니다.
Http로 파일을 보낸다면 요청 본문 필드(body·urlBody·multipartBody)를 하나만 적었고, 각 파일이 플랜별 다운로드 한도 안인지 확인했습니다.- update/patch 경합이 걱정되면
ResourceUpdate나ResourcePatch의version을 씁니다. - 결과를 돌려주려면
Return.value를 명시했습니다.
오류
정의의 형태가 정적 제약을 어겨 저장이 거부될 때 나오는 코드입니다. 값 표현식 규칙을 어긴 코드는 값 표현식의 오류에, 호출·삭제 시 나오는 코드는 엔드포인트의 오류에 있습니다. 모든 리소스에 공통인 코드는 공통 오류를 참조하세요.
| 코드 | 조건 |
|---|---|
WGL400066 | 한 정의에 담은 Cache Statement가 5개를 넘습니다. |
WGL400068 | Cache Statement를 Loop나 ResourceForEach의 블록 안에 두었습니다. |
WGL400067 | Cache Statement의 key에 리터럴이 아니라 { /pointer } 참조를 적었습니다. |
WGL400065 | Cache Statement의 ttl이 허용 범위를 벗어났습니다. |
WGL400063 | Cache Statement에서 action에 해당하지 않는 필드를 적었습니다(Get에 ttl, Set에 defaultValue). |
WGL400060 | ResourceCreate·ResourceDelete와 발행·보관 Statement의 resource에 "ServiceUser"를 적었습니다. 회원 디렉터리는 조회 세 Statement와 ResourceUpdate·ResourcePatch만 받습니다. |
WGL400077 | ResourceCount의 resource에 셀 수 없는 리소스를 적었습니다. Media와 회원 디렉터리(ServiceUser)는 세지 못합니다. |
WGL400037 | EmailSend에 to와 toServiceUser를 함께 적었거나 둘 다 비웠습니다. 정확히 하나만 적습니다. |
WGL400054 | EmailSend의 수신자 합계(to 1명 + cc + bcc)가 50명을 넘습니다. |
WGL400039 | EmailSend의 timeoutMs가 상한을 넘습니다. |
WGL400101 | EmailSend에 body와 templateBody를 함께 적었거나 둘 다 비웠습니다. 정확히 하나만 적습니다. |
WGL400102 | EmailSend의 templateBody.source가 이 Script의 ResourceRead·ResourceFind가 읽은 Content를 가리키는 포인터 하나가 아닙니다. 포인터 앞뒤에 다른 글자가 있어도 거부됩니다. |
WGL400061 | 익명 호출을 허용한 Script(anonymousCallEnabled)에서 읽기 Statement의 where에 createdBy: ":self"를 적었습니다. |
WGL400023 | 한 정의에 담은 SetVar Statement가 10개를 넘습니다. |
WGL400026 | Http Statement의 retry가 상한 2를 넘습니다. |
WGL400087 | Http에 요청 본문 필드(body·urlBody·multipartBody)를 둘 이상 적었습니다. |
WGL400088 | Http의 multipartBody에 part가 하나도 없습니다. |
WGL400089 | multipartBody의 part에 value와 url을 둘 다 적었거나 둘 다 적지 않았습니다. |
WGL400090 | multipartBody의 part에 url 없이 fileName을 적었습니다. |
WGL400036 | ResourceForEach가 처리할 수 있는 항목 수 상한을 넘습니다. |
WGL400094 | secrets에 같은 key를 쓰는 항목이 둘 이상입니다. |
WGL400095 | 한 정의에 담은 secrets가 20개를 넘습니다. |
WGL400086 | secrets의 value가 1~4,096자를 벗어납니다. 오류에는 value가 실리지 않습니다. |
WGL400086 | { /pointer }가 없는 PdfWatermark의 text가 1~64자를 벗어납니다. |
WGL400096 | Media 인제스트 지시의 transformations가 배열이 아니거나, 원소의 type이 PdfWatermark가 아니거나, PdfWatermark에 text가 없거나 객체·배열이거나, color가 #RRGGBB 형식이 아니거나, 작성한 그대로의 text에 쓸 수 없는 글자나 줄바꿈이 들어 있습니다. |
WGL429005 | 한 정의에 담은 전체 Statement 수가 요금제 한도를 넘습니다. |
WGL429006 | 한 정의에 담은 외부 호출(Http·EmailSend) 수가 요금제 한도를 넘습니다. |
WGL403015 | 정의에 담은 Statement가 다루는 리소스와 액션의 권한을 작성자가 갖고 있지 않습니다. 권한이 있어도 그 허용에 contentType·createdBy·tag 필터가 붙어 있으면 저장이 거부됩니다. 조건 없는 허용이라야 합니다. 유일한 예외는 Content Create로, 이때는 contentType 범위를 정한 허용도 인정되며 Statement에 적은 contentType과 대조합니다(Media Create에는 이 예외가 없습니다). 읽기 Statement의 resource에 "ServiceUser"를 적었는데 작성자에게 SETTING_SERVICE_LOGIN이 없는 경우도 이 코드입니다. |
관련 문서
- 값 표현식: 값과 조건 규칙.
- Statement 카탈로그: 각 Statement의 필드와 결과.
- 쿡북: 완결된 예시 모음.
- Script 리소스와 엔드포인트:
Script리소스 구조와/execute등 HTTP 엔드포인트. - Script 개요: 최상위 구조와 한 번의 실행에 주어지는 시간.
