애플리케이션 만들기

리스펙스 규칙 실행하기

완전한 규칙 패키지를 만들고 토파즈에서 호출한 뒤 자원 한도 결과를 처리합니다.

토파즈 애플리케이션에서 리스펙스 규칙을 호출하고 작업량 한도가 소진된 결과를 처리해 보세요. 파일 여섯 개를 만들어 같은 규칙을 서로 다른 한도로 실행합니다.

토파즈를 설치하고, 패키지가 낯설다면 첫 애플리케이션을 먼저 따라 하세요. 평가기는 토파즈에 포함되어 있어 리스펙스를 따로 설치할 필요가 없습니다. 이 연동은 리스펙스 1.20.0을 사용합니다.

규칙은 파일을 읽거나 시스템 시계와 네트워크에 접근할 수 없습니다. 토파즈는 규칙을 한 번 준비합니다. 평가할 때마다 메모리, 전역 변수, 자원 계량기, 실행 기록을 새로 만듭니다.

규칙과 한도 파일 만들기

lispex-first-call 폴더를 만들고 그 안에 rules와 src 폴더를 만드세요. 아래 파일 여섯 개를 저장합니다. 이후 명령은 모두 lispex-first-call에서 실행하세요.

아래 규칙을 rules/approve.lspx로 저장하세요.

LISPEX
(if (< 10 15) "allow" "deny")

이 규칙은 상수 10과 15를 비교해 "allow"를 반환합니다. 입력값은 사용하지 않습니다. 판단을 고정해 두고 평가 완료와 자원 한도 결과를 비교합니다.

다음 한도 파일 전체를 rules/approve.limits.json으로 저장하세요.

JSON
{
  "schema": "topaz.lispex-embed-limits/v1",
  "prepare": {
    "raw_source_bytes": 4096,
    "prepare_work": 1000000,
    "logical_allocation": 1000000,
    "syntax_depth": 64
  },
  "evaluate": {
    "canonical_input_bytes": 4096,
    "eval_work": 10000,
    "logical_allocation": 1000000,
    "semantic_frames": 1000,
    "traversal_depth": 256,
    "output_bytes": 4096,
    "diagnostic_bytes": 4096,
    "transcript_bytes": 4096,
    "transcript_events": 100,
    "result_bytes": 4096
  }
}

두 번째 한도 파일을 rules/semantic-limit-probe.limits.json으로 저장하세요.

JSON
{
  "schema": "topaz.lispex-embed-limits/v1",
  "prepare": {
    "raw_source_bytes": 4096,
    "prepare_work": 1000000,
    "logical_allocation": 1000000,
    "syntax_depth": 64
  },
  "evaluate": {
    "canonical_input_bytes": 4096,
    "eval_work": 1,
    "logical_allocation": 1000000,
    "semantic_frames": 1000,
    "traversal_depth": 256,
    "output_bytes": 4096,
    "diagnostic_bytes": 4096,
    "transcript_bytes": 4096,
    "transcript_events": 100,
    "result_bytes": 4096
  }
}

두 번째 파일은 eval_work를 1로 설정합니다. 이 규칙을 끝내기에는 부족한 작업량입니다. 두 파일의 모든 필드를 유지하세요. 값은 낮출 수 있지만 필드를 추가하거나 빼면 잘못된 문서가 됩니다.

prepare_work는 규칙을 준비하는 작업량을, eval_work는 평가하는 작업량을 셉니다. 같은 규칙·입력·평가기 설정은 같은 작업량을 사용합니다. 경과 시간은 별도로 제어합니다.

애플리케이션 쿼터 정하기

다음 내용을 rules/application.quotas.json으로 저장하세요.

JSON
{
  "schema": "topaz.lispex-application-quotas/v1",
  "concurrent_evaluations": 2,
  "queued_evaluations": 2,
  "total_evaluations": 64,
  "aggregate_input_bytes": 262144,
  "aggregate_result_bytes": 262144,
  "aggregate_output_bytes": 262144,
  "aggregate_transcript_bytes": 262144,
  "aggregate_safety_fuel": 64000000000,
  "prepared_bytes": 3000000,
  "wall_millis": 5000
}

쿼터는 애플리케이션 전체에 적용하는 한도입니다. 이 파일은 동시 평가 2회, 대기 평가 2회, 총 평가 64회를 허용합니다. wall_millis는 평가에 5,000밀리초를 허용합니다. 바이트 필드는 입력·결과·출력·실행 기록의 누적 크기를 제한합니다.

패키지 선언하기

다음 내용을 topaz.toml로 저장하세요.

TOML
[package]
name = "complete_lispex_decision_application"
version = "0.1.0"
language = "5.20"
entry = "src/main.tpz"

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.20"

[lispex]
profile = "lispex/r7rs-rule-current-profile-bounded/1"
application = "topaz/lispex-decision-application/2"
application_quotas = "rules/application.quotas.json"

[[lispex.rule]]
name = "approve"
source = "rules/approve.lspx"
limits = "rules/approve.limits.json"

[[lispex.rule]]
name = "semantic_limit_probe"
source = "rules/approve.lspx"
limits = "rules/semantic-limit-probe.limits.json"

두 규칙 이름은 같은 소스 파일을 사용합니다. approve는 일반 한도를, semantic_limit_probe는 작업량이 1인 한도를 적용합니다.

이 애플리케이션 계약에서는 language와 std를 5.20으로 유지하세요. profile과 application 식별자도 예제 그대로 사용합니다. 프로필은 지원하는 언어 기능과 실행 규칙을 정한 이름입니다. 여기서 bounded는 자원 한도를 나타내는 프로필 식별자의 일부입니다.

각 [[lispex.rule]]에는 name, source, limits 경로를 적습니다. 토파즈는 선언한 이름을 std.lispex.rules의 함수로 제공합니다. approve는 rules.approve()로 호출합니다.

규칙 호출하기

다음 프로그램 전체를 src/main.tpz로 저장하세요.

TOPAZ
import std.lispex { canonicalBytes, defaultLimits, evaluate, valueFromCanonical }
import std.lispex.rules as rules

export function main(인수: Array<string>, 표준입력: string) -> Result<int, string> {
    let 바이트 = Bytes.fromArray([0])?
    let 입력 = match valueFromCanonical(바이트) {
        case Ok(값) => 값
        case Err(_) => return Err("invalid input")
    }
    let 시나리오 = if 인수.length == 0 { "approve" } else { 인수[0] }
    let 규칙 = if 시나리오 == "approve" {
        rules.approve()
    } else if 시나리오 == "semantic-limit" {
        rules.semantic_limit_probe()
    } else {
        return Err("use approve or semantic-limit")
    }
    match evaluate(규칙, 입력, defaultLimits(규칙)) {
        case Ok(판정) => match 판정 {
            case Complete(값) => print("complete:{canonicalBytes(값).toHex()}")
            case LimitExhaustion(이유) => print("limit:{이유}")
            case SemanticFailure(이유) => return Err("rule failed: {이유}")
            case RequestRefusal(이유) => return Err("request refused: {이유}")
        }
        case Err(운영오류) => match 운영오류 {
            case DeadlineExceeded => return Err("application deadline exceeded")
            case TotalEvaluationsExceeded => return Err("application evaluation quota exceeded")
            case _ => return Err("operational fault")
        }
    }
    Ok(0)
}

Bytes.fromArray([0])는 빈 리스펙스 값인 nil을 나타내는 바이트 하나를 만듭니다. valueFromCanonical은 바이트가 공통 값 형식인 lispex.embed-value/v1을 따르는지 검사합니다. 프로그램은 이 값을 입력으로 전달합니다.

defaultLimits는 규칙에 선언된 평가 한도를 읽습니다. evaluate는 LispexSettlement 판정이나 운영 오류를 반환합니다. 판정은 평가 완료, 규칙 실패, 요청 거부, 자원 한도 중 하나입니다. 위 프로그램은 작업량 소진을 결과로 처리하고 애플리케이션 쿼터와 시간 한도 초과는 오류로 보고합니다.

잠금 파일을 만들고 검사·실행하기

topaz.toml이 있는 폴더에서 다음 명령을 실행하세요.

BASH
topaz lock --root .
topaz check --root . --locked
topaz run --root . --locked

lock은 topaz.lock을 쓰고 .topaz/lispex에 규칙을 준비합니다. check는 출력 없이 종료 코드 0으로 끝납니다. run은 아래 한 줄을 출력하고 종료 코드 0으로 끝납니다.

출력
complete:090000000000000001080000000000000005616c6c6f77

프로그램은 결과의 정본 바이트를 16진수로 출력합니다. 이 바이트는 "allow" 문자열 하나를 담은 목록입니다. Complete는 평가기가 규칙 실행을 마쳤다는 뜻입니다.

이번에는 작업량 한도가 작은 규칙을 선택하세요.

BASH
topaz run --root . --locked -- semantic-limit
출력
limit:eval_work

프로그램이 LimitExhaustion을 처리했으므로 종료 코드는 0입니다. 규칙은 eval_work 한도에서 멈췄습니다. 끝까지 실행하려면 rules/semantic-limit-probe.limits.json의 해당 값을 1에서 10000으로 바꾸세요. 잠금·검사·실행 명령을 반복하면 위의 complete 줄이 나옵니다.

이후 예제를 위해 해당 파일의 eval_work를 1로 되돌리고 topaz lock --root .을 다시 실행하세요. 잠금 파일이 갱신되고 semantic-limit는 다시 limit:eval_work를 출력합니다.

커밋 전에 topaz.lock을 확인하세요. 이 파일은 평가기·프로필·인코딩·한도·쿼터·규칙 소스·준비된 규칙과 다이제스트를 기록합니다. 다이제스트는 정확한 바이트를 구분하는 값입니다. 이 값은 topaz lock이 작성하도록 두세요.

매니페스트·규칙·한도·쿼터·평가기·프로필을 바꾸면 잠금 파일을 다시 만드세요. --locked 명령은 불일치를 거부합니다. 잠금 파일 처리는 패키지와 배포, 설치 정보는 툴체인 상태를 참고하세요.

평가 기록 저장하고 확인하기

평가 기록도 저장하려면 evaluateWithEvidence를 사용하세요. 기록을 만들 수 있는 결정론적 결과에는 호출 애플리케이션이 만든 소비자 아티팩트가 함께 반환됩니다.

함수용도
consumerArtifactBytes기록을 저장할 바이트로 변환
consumerArtifactFromBytes저장한 바이트를 읽고 형식 검사
inspectConsumerArtifact규칙을 실행하지 않고 메타데이터와 식별자 읽기
verifyConsumerArtifact구조·다이제스트·평가 매개변수 연결 검사
portableCoreBytes포함된 리스펙스 형식의 기록 추출
freshReplay새 실행 환경에서 기록된 규칙·입력·한도로 다시 실행하고 아티팩트 비교

기록과 그 안의 리스펙스 형식 코어는 호출자가 만든 서명되지 않은 미인증 데이터입니다. 발행자를 명시하지 않으며 공급자 승인, 서명 권한, 구성 요소 승인, 외부 작업 권한을 부여하지 않습니다. 기록 내용 검사와 외부 작업 권한 확인은 별도로 수행하세요.

로컬 요청 거부, 취소, 대기열 포화, 안전을 위한 중단, 엔진 실패에는 이식 가능 코어가 생성되지 않습니다. freshReplay는 새 아티팩트가 저장한 아티팩트와 일치하면 성공합니다.

관리 중인 docs/samples/guides/lispex-application/first-call 샘플에는 위 파일 여섯 개가 있습니다. 상위 폴더의 샘플은 반복 평가, 기록 저장·검사·재실행, 상태 격리, 총 평가 쿼터도 다룹니다. 그 샘플의 all 시나리오는 65번째 호출이 거부되면 끝납니다. 이 페이지의 첫 호출 프로그램은 approve와 semantic-limit를 받습니다.

네이티브 제품 빌드하기

Rust 빌드 도구를 설치한 뒤 패키지 폴더 밖에 릴리스 빌드를 만드세요.

BASH
topaz build --root . --locked --release --out-dir ../complete-product

Linux나 macOS에서는 생성된 프로그램을 다음과 같이 실행합니다.

BASH
../complete-product/target/release/program
../complete-product/target/release/program semantic-limit

Windows에서는 다음 명령을 사용합니다.

POWERSHELL
..\complete-product\target\release\program.exe
..\complete-product\target\release\program.exe semantic-limit

첫 명령은 complete 줄을, 두 번째 명령은 limit:eval_work를 출력합니다. 종료 코드는 모두 0입니다. 빌드한 운영체제와 아키텍처에서 실행 파일을 사용하세요.

실행 환경 고르기

경로동작
interpretertopaz run으로 잠긴 애플리케이션 실행
native지원하는 네이티브 릴리스 대상에서 실행
raw-web과 worker-web출력 파일을 쓰기 전에 거부
no-capability실행하거나 출력 파일을 쓰기 전에 거부
mcp-empty-component-set실행 전에 거부

네이티브 릴리스 대상은 Windows x64, Linux x64·arm64, macOS x64·arm64입니다. 지원하지 않는 경로는 다른 실행 환경으로 전환하지 않고 실패합니다.

기존 토파즈 5.18 패키지는 자체 평가기·프로필·계약·준비된 규칙 식별자를 가진 embedded-core 호환 프로필을 사용합니다. 위 애플리케이션은 lispex/r7rs-rule-current-profile-bounded/1을 사용합니다. 한 프로필을 선택해도 다른 프로필로 대체하지 않습니다.

관련 문서