실행 시맨틱, 제약, 보안

최종 수정: 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 등) 중 하나라도 있으면 executionModeAsync 강제입니다. 이 셋은 별개의 능력이고, 아래 정적 제약에서 카운트 대상이 갈립니다.

실행 시맨틱

Guard (사전 조건)

전용 guard 문은 없습니다. Ifthen:[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 경합ResourceUpdateResourcePatchversion으로 좁힙니다. version(값표현식, Int)을 주면 대상의 현재 sys.version일치할 때만 갱신하고, 불일치는 버전 충돌 에러로 abort됩니다(Try/catch로 국소 처리 가능). 생략하면 검사 없이 last-write-wins입니다. 보통 ResourceReadResourcePageRead로 먼저 읽어 그 sys.version을 넘깁니다(쿡북의 낙관적 잠금 CAS 참조).

origin 기준 쓰기

쓰기는 항상 origin(draft)에 반영되고, delivery(CDA/ACDA) 노출은 publish로 제어합니다(ResourceCreate/ResourceUpdate/ResourcePatchpublish, 또는 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가 있으면 executionModeAsync해당 없음
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 강제와 Loop body 금지는 Http와 똑같이 적용됩니다.

시간 예산 (런타임)

모드기본 예산
Sync10초 (syncTimeoutMs)
Async60초 (asyncTimeoutMs)

플랜별 개수 한도

ScriptBillable 리소스로, Organization당 개수가 플랜별로 제한됩니다.

PlanScript 개수
Free3
Basic10
Pro50
Enterprise무제한

한도에 도달하면 새 Script 생성이 거부됩니다(다른 Billable 자원과 동일 경로).

보안 모델

secret 헤더

Http.headerssecret:true 항목은 CMA(관리자) 전용으로, 최종 사용자(ServiceUser)에게 노출되지 않고 전송 직전에만 복호화됩니다. LLM API 키 같은 비밀을 여기에 둡니다(App Bundle로 패킹될 때도 secret 값은 마스킹되어 원본 space 밖으로 나가지 않습니다).

실행 신원과 인가

  • 실행 신원: 실행 중 모든 리소스 작업은 /execute를 호출한 사용자 신원으로 수행됩니다. 만들어지거나 수정되는 리소스의 createdBy/updatedBy가 호출자이고, createdBy: ":self" 스코프도 호출자를 기준으로 풀립니다.
  • 인가 경계는 둘이며, 런타임에는 statement마다 리소스 권한을 재검사하지 않습니다.
    1. 저작 시(저장): Script를 저장할 때, 작성자가 그 statement들이 쓰는 리소스와 액션 권한을 실제로 갖는지 검사합니다. 하나라도 없으면 저장이 거부됩니다(WGL403015). 즉, 권한 없는 작업이 담긴 Script는 애초에 저장되지 않습니다.
    2. 호출 시(/execute): 호출자의 Script Execute 권한만 검사합니다. 권한이 없으면 403입니다. 통과하면 statement별 리소스 권한은 런타임에 다시 확인하지 않고 실행합니다. 프로그래밍의 함수 실행 권한과 같은 방식입니다. 함수를 실행할 권한만 있으면 그 안의 개별 작업 권한은 다시 묻지 않습니다.
  • 소유권 스코프: where 필터의 createdBy: ":self"는 "현재 호출자가 만든 것만"을 뜻합니다(예: 내 지갑만 조회).
  • 위임된 권한 (작성 주의): 위 두 경계를 합치면, Script 실행은 작성자의 권한을 위임받아 수행되는 것과 같습니다. 호출자는 Execute 하나만 있으면 되고, Script 안 statement는 작성자가 저장 시 인가받은 범위에서 그대로 실행됩니다. 따라서 호출자가 스스로는 하지 못하는 리소스 작업도 Script를 통해 일어날 수 있습니다. 작성자에게 부여된 권한이 곧 그 Script의 영향 범위이므로, Script에 담는 동작은 신중히 정합니다.

요약 체크리스트

저장 전에 다음을 확인합니다.

  • 외부 호출(Http)이나 Media 파일 인제스트가 있으면 executionMode"Async"입니다.
  • Loop body 안에 외부 호출을 넣지 않았습니다.
  • 외부 호출 3개 이하, SetVar 5개 이하, 전체 statement 15개 이하입니다.
  • secret 값은 Http.headerssecret:true로만 넣었습니다.
  • 되돌릴 수 없는 작업(외부 호출)은 되도록 뒤에 두었습니다.
  • update/patch 경합이 걱정되면 ResourceUpdateResourcePatchversion을 씁니다.
  • 결과를 돌려주려면 Return.value를 명시했습니다.