Создание приложений

Запуск правил Лиспекса

Принятие решений внутри программы с помощью внешних правил без предоставления доступа к сети, системным часам и файловой системе.

Некоторым логическим решениям не место в исходном коде. Это может быть одобрение запроса, категоризация клиента или начисление баллов заявке. Подобные правила обновляются по собственному графику и часто формируются сторонними специалистами. В этом руководстве показано, как изолировать такое правило в отдельном файле, передавать ему входные данные и получать результат вычисления, предотвращая доступ правила к остальным ресурсам системы.

Правила пишутся на Лиспексе. Это детерминированный Лисп для исполняемых правил, где одинаковые правило и входные данные всегда дают идентичный результат. Топаз использует модель «одна подготовка — много вычислений» (prepare-once/evaluate-many) и предоставляет вашему коду стандартную функцию для вызова. Подготовленное правило не обращается к файловой системе, системным часам или сети. Его выполнение прерывается при исчерпании выделенного лимита операций. Собранный таким образом пакет называется приложением решений на Лиспексе с полным текущим профилем, и именно он формируется в данном руководстве.

Материал рассчитан на разработчиков, уже имеющих опыт сборки пакетов в Топазе. При отсутствии такого опыта начните с раздела Первое приложение.

Напишите правило

Правило создаётся в файле с исходным кодом на Лиспексе. Данное правило сравнивает два числа и возвращает одну из двух строк. Сохраните его как rules/approve.lspx.

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

Собираемый ниже пакет дополняют ещё три правила. Файл rules/classify.lspx возвращает числовое значение вместо строки.

LISPEX
(+ 20 22)

Правило rules/deadline-probe.lspx выполняет бесконечный цикл, позволяя проверить механизм принудительной остановки по таймауту приложения.

LISPEX
(letrec ((loop (lambda () (loop)))) (loop))

Четвёртое правило повторно использует файл rules/approve.lspx с более жёстким лимитом, поэтому отдельный файл для него не требуется.

Задайте два вида лимитов

Ограничение выполнения задаётся двумя документами. Документ лимитов определяет параметры для отдельного правила, а документ квот ограничивает работу приложения в целом.

Файл rules/approve.limits.json задаёт верхние границы подготовки и вычисления для одного правила. В полном документе должны присутствовать всё поля. Их значения можно уменьшать, но удалять или добавлять поля запрещено. В приведенном фрагменте оставлены два параметра ограничений.

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

Параметры prepare_work и eval_work детерминированно учитывают объём вычислений без привязки к реальному времени, благодаря чему одинаковое правило с идентичным входом потребляет равное количество ресурсов на любой платформе.

Каждое правило требует собственного документа лимитов. Файлы rules/classify.limits.json и rules/deadline-probe.limits.json используют ту же структуру. В документе rules/semantic-limit-probe.limits.json параметр eval_work равен 1, что заведомо недостаточно для завершения выполнения. Это сделано намеренно, чтобы продемонстрировать обработку ситуации при исчерпании лимита ресурсов.

Документ rules/application.quotas.json задаёт ограничения на уровне хоста. Он разрешает два одновременных вычисления, ещё два вызова в очереди, 64 вычисления суммарно и предельный таймаут по астрономическому времени.

Различие между этими документами проявляется при остановке выполнения. Правило, превысившее лимит eval_work, исчерпывает семантический предел, но вычисление всё равно возвращает результат, доступный для разбора в коде. Если же приложение превышает общую квоту, происходит отказ в вызове. Это разные типы результатов, и их одинаковая обработка приведет к ошибкам в логике.

Объявите пакет

Манифест объединяет все компоненты. В нём определяются версия языка Топаза и стандартная библиотека, объявляется нативная целевая платформа, указываются полный текущий профиль Лиспекса, контракт приложения, файл квот и список всех правил. Сохраните манифест под именем topaz.toml.

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

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.19"

[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 = "classify"
source = "rules/classify.lspx"
limits = "rules/classify.limits.json"

[[lispex.rule]]
name = "deadline_probe"
source = "rules/deadline-probe.lspx"
limits = "rules/deadline-probe.limits.json"

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

Параметры language и std фиксируются контрактом приложения. Инструментарий с несоответствующим языковым режимом отклонит пакет на этапе подготовки. Раздел Состояние инструментария содержит сведения о текущих версиях компилятора, языковом режиме и среде выполнения.

Параметр profile выбирает точный вычислитель полного текущего профиля с идентификатором lispex/r7rs-rule-current-profile-bounded/1. Слово bounded в токене поставщика обозначает фиксированные ресурсные границы, а не меньший профиль совместимости Топаза 5.18. Параметр application выбирает контракт 5.19, определяющий допустимый способ поставки готового пакета. Оба значения являются фиксированными идентификаторами, а не настраиваемыми опциями.

Каждая секция [[lispex.rule]] содержит три обязательных поля. Имя name преобразуется в сгенерированную функцию в std.lispex.rules, превращая approve в rules.approve(). Это не путь к файлу во время выполнения и не селектор компонента. Две разные записи могут ссылаться на один файл исходного кода (как в случае с approve и semantic_limit_probe), сохраняя при этом индивидуальные лимиты.

Заблокируйте пакет

Операция блокировки однократно подготавливает каждое правило и фиксирует результаты. Запускайте её из корневого каталога пакета.

BASH
topaz lock --root .

Дайджест компонента или подготовленного артефакта не следует вводить вручную. Проверьте файл topaz.lock перед его фиксацией. Секция [lispex] фиксирует профиль, контракт приложения, компонент, вычислитель, ABI, кодек значений, модель счётчиков, контракт артефактов, адаптер, квоты, целевую диспозицию и каталог сгенерированных дескрипторов. Каждая запись [[lispex.rule]] закрепляет исходный код правила, его лимиты, параметры подготовки и итоговый артефакт.

Компонентом в данном контексте является сам вычислитель. Файл блокировки фиксирует его дайджест, предотвращая подмену при последующих сборках.

При изменении манифеста, исходного кода правил, файла лимитов, квот, компонента или профиля необходимо пересоздать файл блокировки. Выполнение сборки с флагом --locked приведет к ошибке при любых расхождениях и не позволит автоматически сформировать новые правила.

Вызовите правило из кода на Топазе

Блокировка генерирует API в модуле std.lispex и создаёт соответствующие функции для каждого объявленного правила в std.lispex.rules.

TOPAZ
let решение = evaluate(rules.approve(), input, defaultLimits(rules.approve()))
let запись = evaluateWithEvidence(
    rules.approve(),
    input,
    defaultLimits(rules.approve()),
)

Функция evaluate возвращает результат вычисления. Он представляет собой типизированный результат, определяющий, завершилось ли правило успешно, отклонено ли оно по внутренним условиям или прервано из-за исчерпания лимита. Функция defaultLimits извлекает значения лимитов, зафиксированные в файле блокировки для данного правила, не позволяя запросить ресурсы сверх объявленных в пакете.

Параметр input задаётся в виде канонического значения — байтового представления, согласованного обеими сторонами, что гарантирует однозначную интерпретацию данных. Каждая операция выполняется с изолированной памятью, сброшенными глобальными переменными, счётчиками и буфером транскрипта, предотвращая передачу состояния между вызовами правил.

Используйте evaluate, когда требуется только результат вычисления. Применяйте evaluateWithEvidence, если детерминированный результат должен также сформировать запись свидетельства.

Сохраните, проверьте и повторите свидетельство

При успешном завершении функция evaluateWithEvidence дополнительно формирует артефакт потребителя. Этот артефакт фиксирует детали вычисления, позволяя сохранить его, передать внешней системе и проверить впоследствии без необходимости доверять создавшему его приложению.

Полный жизненный цикл включает шесть основных вызовов.

  1. consumerArtifactBytes сериализует артефакт в массив байтов для сохранения.
  2. consumerArtifactFromBytes десериализует сохранённые байты с выполнением валидации.
  3. inspectConsumerArtifact возвращает стабильные идентификаторы без запуска выполнения.
  4. verifyConsumerArtifact проверяет целостность структуры, дайджесты и внутренние связи.
  5. portableCoreBytes извлекает ядро в формате Лиспекса, если оно присутствует в артефакте.
  6. freshReplay повторно вычисляет заблокированное правило с исходными вводными данными в новом гостевом окружении и принимает только полностью совпадающий артефакт.

Будьте осторожны с выводами из артефакта. Сам артефакт и его переносимое ядро формируются на стороне потребителя и не подлежат аутентификации. Они не содержат сведений об издателе, цифровой подписи, подтверждений от поставщика, прав доступа компонента или разрешений на выполнение внешних операций. При операционных отказах, отмене, защитном прерывании или сбое движка переносимого ядра артефакты не создаются.

Проверьте и запустите пакет

Сначала выполните проверку пакета, а затем его запуск на основе файла блокировки.

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

Аргумент all запускает тестовый сценарий, полностью проверяющий интеграционные границы. Он выполняет 24 стандартных вычисления, подтверждает изоляцию состояния между правилами, доводит одно правило до исчерпания семантического лимита и проверяет отклонение 65-го вызова при установленной квоте приложения в 64 операции. Полная версия пакета при вызове all выводит ровно одну строку.

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

Вывод любого другого текста или ненулевой код завершения свидетельствует об ошибке проверки.

Правило deadline_probe намеренно исключено из сценария all. Проверка таймаутов осуществляется отдельными релизными тестами. Они дублируют пакет, уменьшают квоту астрономического времени приложения со 100 мс до 1 мс, обновляют привязку квоты в копии файла блокировки и проверяют ожидаемый результат DeadlineExceeded как при автономном запуске в интерпретаторе, так и при выполнении нативного продукта без исходных файлов. После этого стандартное приложение с таймаутом 100 мс должно успешно завершить работу. Разделение этих тестовых случаев гарантирует, что приведённая выше строка вывода не зависит от производительности текущего оборудования.

Соберите нативный продукт

Скомпилируйте оптимизированный управляемый нативный продукт в каталог за пределами корневой директории пакета.

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

Запускайте полученный исполняемый файл в той же операционной системе и на той же архитектуре, где производилась сборка.

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

Для Windows используйте следующую команду.

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

Нативный продукт должен вывести аналогичную строку результатов. Успешная компиляция не обеспечивает переносимость полученного исполняемого файла на другие целевые платформы.

Где готовое приложение может работать

Контракт приложения определяет фиксированный перечень допустимых маршрутов поставки. Неподдерживаемый маршрут отклоняется до начала выполнения или записи данных, без автоматического переключения на интерпретатор или нативный продукт.

МаршрутДиспозиция
interpreterПоддерживается для заблокированного приложения полного профиля
nativeПоддерживается только для утверждённых нативных целевых платформ
generated-pythonОтклоняется до записи артефакта
raw-webОтклоняется до записи артефакта
worker-webОтклоняется до записи артефакта
managed-webОтклоняется до записи артефакта
http-serviceОтклоняется до записи артефакта
no-capabilityОтклоняется до выполнения или записи артефакта
mcp-empty-component-setОтклоняется до выполнения

Ограниченный контракт Топаза 5.18 сохраняется только как неизменяемый маршрут совместимости для существующих пакетов. Он продолжает использовать собственные компонент, профиль, контракт и идентичность подготовленного артефакта; селектор или резервный маршрут не может подменить им приложение полного текущего профиля из этого руководства.

Связанная документация