Build Applications

Build a Bounded Lispex Decision Application

Bind restricted Lispex rules to a locked Topaz 5.18 package, evaluate many inputs, and verify consumer evidence.

Topaz 5.18 lets one checked application prepare named Lispex rules once and evaluate many canonical values. The boundary is deliberately narrow. The package fixes lispex/r7rs-rule-embedded-core/1, the embedded component, every rule source and limit record, and the application-wide quota record in topaz.lock.

Start from the maintained sample in docs/samples/guides/lispex-application. It contains the exact manifest, lock, generated rule handles, prepared rule artifacts, two Topaz modules, and four reached rules used below.

Declare the application

The manifest selects Topaz and the standard library together. It also names the restricted Lispex profile and application contract.

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

[dependencies]
std = "5.18"

[lispex]
profile = "lispex/r7rs-rule-embedded-core/1"
application = "topaz/lispex-decision-application/1"
application_quotas = "rules/application.quotas.json"

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

Each additional [[lispex.rule]] entry follows the same shape. Rule names become generated functions in std.lispex.rules; they are not runtime file paths or component selectors.

Write a rule and finite limits

rules/approve.lspx is regular Lispex source within the selected restricted profile.

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

rules/approve.limits.json fixes preparation and evaluation ceilings. The complete sample retains every field; this excerpt shows the two work limits.

JSON
{
  "schema": "topaz.lispex-embed-limits/v1",
  "prepare": {
    "prepare_work": 1000000
  },
  "evaluate": {
    "eval_work": 10000
  }
}

rules/application.quotas.json separately limits the application host. The sample allows two active evaluations, two queued evaluations, 64 total evaluations, and a finite wall deadline. Per-rule semantic exhaustion and an application refusal are different typed outcomes.

Generate and inspect the lock

Generate the lock from the package root. Do not enter component or prepared artifact digests by hand.

BASH
topaz lock --root .

Inspect topaz.lock before committing it. The [lispex] section fixes the profile, application contract, component, evaluator, ABI, value codec, meter, artifact contract, adapter, quotas, target disposition, and generated handle catalog. Every [[lispex.rule]] row fixes the source, limits, preparation request and submission, and prepared artifact.

Changing the manifest, rule source, limits, quotas, component, or profile requires a newly generated lock. A build with --locked refuses drift rather than preparing a different rule silently.

Check and run the package

Use the locked interpreter path first.

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

The maintained all scenario evaluates 24 ordinary inputs, checks isolated rule state, reaches semantic exhaustion, and refuses the 65th application evaluation. It prints exactly:

all:default:24:evidence:verified:replayed:isolation:2:complete:semantic-limit:exhausted:aggregate-quota:64:refused

Any other text or a nonzero exit code is a failed observation.

The locked deadline_probe is intentionally separate from all. The release court copies the exact package, changes only the application wall quota from 100 milliseconds to 1 millisecond, rebinds that quota in the copied lock, and requires DeadlineExceeded in dedicated interpreter and relocated source-free native products. It then runs the ordinary 100-millisecond application successfully. Q1 remains the authority for cleanup inside one application. Ordinary sample output therefore makes no timing assumption about the machine running it.

Inspect, verify, and replay consumer evidence

Import the generated API from std.lispex and the generated rule handle from std.lispex.rules.

TOPAZ
let settlement = evaluate(rules.approve(), input, defaultLimits(rules.approve()))
let recorded = evaluateWithEvidence(
    rules.approve(),
    input,
    defaultLimits(rules.approve()),
)

For an eligible deterministic settlement, the complete sample then uses this exact lifecycle:

  1. consumerArtifactBytes stores the whole consumer artifact.
  2. consumerArtifactFromBytes performs checked intake of stored bytes.
  3. inspectConsumerArtifact reports stable identities without executing.
  4. verifyConsumerArtifact verifies structure, digests, and bindings.
  5. portableCoreBytes extracts the Lispex-format core when one exists.
  6. freshReplay evaluates the locked rule and input in a fresh guest and accepts only the same complete artifact.

The artifact and portable core are consumer-produced and unauthenticated. They contain no issuer, provider approval, signing authority, component admission, or permission to perform an external action. Operational refusal, cancellation, safety preemption, and engine failure do not create a portable core.

Build and run the native product

Build an optimized managed native product outside the package root.

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

Run the result on the same OS and architecture that built it.

BASH
../bounded-product/target/release/program all

On Windows, run:

POWERSHELL
..\bounded-product\target\release\program.exe all

The native product must print the same exact observation. A successful build does not make that executable portable to a different target.

Know the route boundary

The application contract has a closed route set.

RouteDisposition
interpreterSupported for the locked bounded application
nativeSupported only on admitted Topaz 5.18 native release targets
generated-pythonRefused before artifact output
raw-webRefused before artifact output
worker-webRefused before artifact output
managed-webRefused before artifact output
http-serviceRefused before artifact output
no-capabilityRefused before execution or artifact output
mcp-empty-component-setRefused before execution

There is no fallback from a refused route to the interpreter or native product. Full-profile Lispex uses a separate component, profile, contract, target court, and admission. It does not widen this restricted profile.

Continue with Rust Backend, Python Backend, and Lispex Evaluator and LIT.