실행 시맨틱, 제약, 보안
최종 수정: 2026년 7월 21일
Script가 런타임에 어떻게 동작하는지(순서, 트랜잭션, 에러, 잠금), 저장 시 어떤 정적 제약을 받는지, 그리고 보안 모델을 정리합니다. 문법은 Statement 카탈로그와 값 표현식, 실전 조합은 쿡북을 참조하세요.
실행 순서와 모드
statements는 위에서 아래로 순차 실행됩니다.Return에 도달하면 그 지점에서 종료합니다.- Sync는 요청을 처리하는 경로에서, Async는 백그라운드에서 실행됩니다. 실행 위치의 구분일 뿐, 결과는 어느 쪽이든
Return값입니다(호출 응답 형태는 Script 개요의 요청과 응답, 실행 모드 참조). - 능력(capability)에서 모드로: statement 트리에
ExternalIo(Http외부 호출),MediaIngest(Media 파일 인제스트.fields.file의{ source, encoding }. url·base64 공통),LongRunning(대량 Loop 등) 중 하나라도 있으면executionMode는Async강제입니다. 이 셋은 별개의 능력이고, 아래 정적 제약에서 카운트 대상이 갈립니다.
실행 시맨틱
Guard (사전 조건)
전용 guard 문은 없습니다. 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를 만들어서, 이를 가리키던 참조가 깨집니다(생성 롤백은 쉽고, 수정은 before-image가 필요합니다). - 외부 효과(
Http)는 비가역입니다(이미 나간 호출과 과금은 되돌릴 수 없습니다). - 프로세스 크래시 시 미보상 상태(orphan)가 남을 수 있습니다.
진짜 원자성이 필요하면 사용자가 Script로 직접 보상하거나, 되돌릴 수 없는 작업(외부 호출 등)을 맨 마지막에 배치합니다. "연쇄는 되는데 롤백은 안 되면서 안전해 보이는" 순서가 가장 위험합니다.
낙관적 잠금
update/patch 경합은 ResourceUpdate와 ResourcePatch의 version으로 좁힙니다. version(값표현식, Int)을 주면 대상의 현재 sys.version과 일치할 때만 갱신하고, 불일치는 버전 충돌 에러로 abort됩니다(Try/catch로 국소 처리 가능). 생략하면 검사 없이 last-write-wins입니다. 보통 ResourceRead나 ResourcePageRead로 먼저 읽어 그 sys.version을 넘깁니다(쿡북의 낙관적 잠금 CAS 참조).
origin 기준 쓰기
쓰기는 항상 origin(draft)에 반영되고, delivery(CDA/ACDA) 노출은 publish로 제어합니다(ResourceCreate/ResourceUpdate/ResourcePatch의 publish, 또는 ResourcePublish/ResourceUnpublish).
무엇이 실패인가
- 진짜 실패는 statement 런타임 에러입니다:
Http의 최종 status가 400 이상(4xx·5xx.ignoreStatusCode: true면 실패 아님)이거나 타임아웃, 응답 본문이 10MiB 초과, 리소스 작업 실패(대상 없음, 버전 충돌, 미지원 연산 등). 이런 실패는 엔진이 abort하고 보상하며,Try/catch/finally로 국소 처리할 수 있습니다. Return은 에러가 아니라 정상 조기 종료입니다.catch대상이 아닙니다(사용자 throw 개념 없음).catch안에서는/error로{ message, statement }를 참조합니다.
서버측 집계 없음
count, sum, group-by 전용 서버 작업은 없습니다. ResourcePageRead 순회와 SetVar/JsonLogic으로 계산하며, 따라서 fetch 크기와 maxIterations에 묶입니다(수백만 건 집계엔 부적합).
대기와 지연 없음
Script에는 Delay 문이 없습니다. Script는 요청 경로(Sync)나 백그라운드(Async)에서 한 번 실행되고 끝이며, 외부 job이 끝날 때까지 내부에서 대기하거나 폴링하지 않습니다(Async 결과는 별개입니다. 호출자가 202로 받은 requestId로 폴링해 Return 값을 얻습니다).
정적 제약 (저장 시 검증)
아래는 Script를 저장(생성/수정)하는 시점에 검사됩니다. 위반하면 저장이 거부됩니다(런타임이 아니라 저작 시 실패).
| 제약 | 기본값 |
|---|---|
외부 I/O가 있으면 executionMode는 Async | 해당 없음 |
Loop body 안에서 Http 외부 호출과 Media 파일 인제스트 금지 | 해당 없음 |
한 정의당 Http 외부 호출 최대 | 3 (maxExternalIo) |
한 정의당 SetVar 최대(중첩 포함) | 5 (maxSetVar) |
| 한 정의당 전체 statement 최대(중첩 포함) | 15 (maxStatements) |
Http.retry 상한 | 2 (maxHttpRetry) |
한도는 서버 설정(weegloo.core.script.*)으로 조정할 수 있습니다(위는 기본값).
Media 파일 인제스트는
MediaIngest능력으로,Http외부 호출(ExternalIo)과 달리maxExternalIo(3) 한도에 포함되지 않습니다. 다만Async강제와Loopbody 금지는Http와 똑같이 적용됩니다.
시간 예산 (런타임)
| 모드 | 기본 예산 |
|---|---|
| Sync | 10초 (syncTimeoutMs) |
| Async | 60초 (asyncTimeoutMs) |
플랜별 개수 한도
Script는 Billable 리소스로, Organization당 개수가 플랜별로 제한됩니다.
| Plan | Script 개수 |
|---|---|
| Free | 3 |
| Basic | 10 |
| Pro | 50 |
| Enterprise | 무제한 |
한도에 도달하면 새 Script 생성이 거부됩니다(다른 Billable 자원과 동일 경로).
보안 모델
secret 헤더
Http.headers의 secret:true 항목은 CMA(관리자) 전용으로, 최종 사용자(ServiceUser)에게 노출되지 않고 전송 직전에만 복호화됩니다. LLM API 키 같은 비밀을 여기에 둡니다(App Bundle로 패킹될 때도 secret 값은 마스킹되어 원본 space 밖으로 나가지 않습니다).
실행 신원과 인가
- 실행 신원: 실행 중 모든 리소스 작업은
/execute를 호출한 사용자 신원으로 수행됩니다. 만들어지거나 수정되는 리소스의createdBy/updatedBy가 호출자이고,createdBy: ":self"스코프도 호출자를 기준으로 풀립니다. - 인가 경계는 둘이며, 런타임에는 statement마다 리소스 권한을 재검사하지 않습니다.
- 저작 시(저장): Script를 저장할 때, 작성자가 그 statement들이 쓰는 리소스와 액션 권한을 실제로 갖는지 검사합니다. 하나라도 없으면 저장이 거부됩니다(
WGL403015). 즉, 권한 없는 작업이 담긴 Script는 애초에 저장되지 않습니다. - 호출 시(
/execute): 호출자의 Script Execute 권한만 검사합니다. 권한이 없으면403입니다. 통과하면 statement별 리소스 권한은 런타임에 다시 확인하지 않고 실행합니다. 프로그래밍의 함수 실행 권한과 같은 방식입니다. 함수를 실행할 권한만 있으면 그 안의 개별 작업 권한은 다시 묻지 않습니다.
- 저작 시(저장): Script를 저장할 때, 작성자가 그 statement들이 쓰는 리소스와 액션 권한을 실제로 갖는지 검사합니다. 하나라도 없으면 저장이 거부됩니다(
- 소유권 스코프:
where필터의createdBy: ":self"는 "현재 호출자가 만든 것만"을 뜻합니다(예: 내 지갑만 조회). - 위임된 권한 (작성 주의): 위 두 경계를 합치면, Script 실행은 작성자의 권한을 위임받아 수행되는 것과 같습니다. 호출자는 Execute 하나만 있으면 되고, Script 안 statement는 작성자가 저장 시 인가받은 범위에서 그대로 실행됩니다. 따라서 호출자가 스스로는 하지 못하는 리소스 작업도 Script를 통해 일어날 수 있습니다. 작성자에게 부여된 권한이 곧 그 Script의 영향 범위이므로, Script에 담는 동작은 신중히 정합니다.
요약 체크리스트
저장 전에 다음을 확인합니다.
- 외부 호출(
Http)이나 Media 파일 인제스트가 있으면executionMode는"Async"입니다. Loopbody 안에 외부 호출을 넣지 않았습니다.- 외부 호출 3개 이하,
SetVar5개 이하, 전체 statement 15개 이하입니다. - secret 값은
Http.headers의secret:true로만 넣었습니다. - 되돌릴 수 없는 작업(외부 호출)은 되도록 뒤에 두었습니다.
- update/patch 경합이 걱정되면
ResourceUpdate나ResourcePatch의version을 씁니다. - 결과를 돌려주려면
Return.value를 명시했습니다.
관련 문서
- 값 표현식: 값과 조건 규칙.
- Statement 카탈로그: 각 문의 필드와 결과.
- 쿡북: 완결된 예시 모음.
- Script 리소스와 엔드포인트:
Script리소스 구조와/execute등 HTTP 엔드포인트. - Script 개요: 최상위 구조와 실행 모드.
