Перейти к содержанию

Выражения

Все выражения языка процессов — условия, ключи, вычисляемые поля, сроки, назначения, входы таблиц решений, якоря памяти — пишутся на одном языке: CEL (Common Expression Language) в профиле платформы. Статья для авторов процессов: переменные, типы, функции календаря, ограничения и перевод прежних синтаксисов. Обоснование — CP-ADR-0075.

Профиль cp/1

Профиль — это окружение (переменные и их типы), набор функций, запреты и лимиты. Ядро записывает имя профиля в версию определения (expressionProfile). Новая функция, не меняющая прежних значений, остаётся в cp/1; изменение смысла — новый профиль, и определение на cp/1 вычисляется по cp/1, пока живы его экземпляры.

Одно имя профиля

В описаниях схемы каталога профиль может называться по имени продукта с тем же номером /1 — это тот же профиль. Пакеты имя профиля не хранят.

Где встречаются выражения:

Место Что даёт выражение
when, entry, exit, вехи, where триггера bool
start.key, correlate[].key ключ экземпляра
set, output.as, export.as, start.set, correlate[].set значение поля данных
input.from, call.input, decide.input вход шага
due.at, timeout.at, wait.at, at таймеров, after эскалаций timestamp или duration
assign[].expr, approvers[].expr id principal'а, agent:<ключ> или role:<slug>
separationOfDuties список principal'ов
title, raise.detail, suspend.reason строка
входы таблиц решений (inputs[].expr) значение входа
memory, якоря recall и context, remember ключи и значения для базы знаний

В YAML выражение — строка. Строковый литерал внутри выражения берётся в одинарные кавычки: "'invoice:' + data.number".

Переменные

Переменная Что Тип
data данные экземпляра из JSON Schema spec.data
event событие входа: id, type, time, entityType, entityId, actorId, correlationId, payload payload — из каталога событий для event: или map(string, dyn)
step результат шага: id, skill, status, result, error.code, error.message; в блоке — последний завершённый шаг потока, в output.as — сам шаг выход скилла по его схеме, форма задачи, выходы таблицы, ответ recall
task задача шага: id, publicId, typeKey, title, status, assigneeId, customFields, artifacts… customFields — по fieldSchema типа задачи
stage stage.<id>.completed, stage.<id>.active (или stage["<id>"]) bool
instance id, key, version, startedAt, clock clock — время текущего входа

Кроме переменных профиля, по месту видны привязки: milestone.<id> (вехи стадий), имя ошибки из try.catch[].as в её обработчике и compensated (компенсируемый шаг) внутри onCompensate.

step.result зависит от вида шага:

Шаг step.result
human поля формы шага (или fieldSchema типа задачи)
approve {outcome, approvedBy, rejectedBy}
call скилла выход скилла по его схеме
decide выходы строки таблицы; у collect — {items: [...]}
recall {nodes, edges, truncated}
listen {option, event}

Типы из схемы данных

Типы выражений выводятся из JSON Schema данных процесса:

JSON Schema CEL
string string
integer int
number double
boolean bool
array list(T); отсутствующий массив — пустой список
object с properties запись с объявленными полями — обращение к необъявленному полю — ошибка при публикации
object без properties map(string, dyn)
format: date-time timestamp
format: duration duration
oneOf, $ref, смесь типов dyn — проверяется при вычислении

Отсутствующее или null скалярное поле читается как null и падает при использовании. Для необязательных полей:

has(data.review)                         // есть ли поле
data.?review.orValue('')                 // значение или '' — результат не должен остаться optional
data.?approval.orValue('') == 'approved'

Незаданное поле времени без has() или .? — ошибка вычисления: иначе оно читалось бы как 1970 год.

Функции

Календарь

Функция Что возвращает
cal.addWorkdays(ts, n) n-й рабочий день после дня ts (при n < 0 — до него); сам день ts не считается, n = 0 — тот же момент; время суток сохраняется в поясе календаря
cal.isWorkday(ts) рабочий ли день ts
cal.workdaysBetween(a, b) число рабочих дней в (a, b], при b < a — со знаком минус

Последний аргумент — ключ календаря (cal.addWorkdays(ts, -3, 'ru')). Его можно опустить, если у процесса есть spec.calendar; без календаря процесса короткая форма — ошибка типа при публикации.

  • День рабочий, если он в workdays года; иначе — если он не в holidays и не выходной день недели. Для года, которого в календаре нет, известны только выходные дни недели.
  • Вычисление, которое задело предварительный год (provisional: true) или год вне календаря, помечается «предварительно».
due: {at: "cal.addWorkdays(data.submissionEnd, -3)"}   # за три рабочих дня до даты
when: cal.workdaysBetween(instance.clock, data.submissionEnd) < 3

Время и длительности

  • duration("P3D") принимает литерал ISO 8601 (недели, дни, часы, минуты, секунды; годы и месяцы — ошибка типа) и форму CEL (duration("72h")). Строка ISO, вычисленная во время работы, не разбирается — длительности данных типизируются схемой (format: duration).
  • timestamp("2026-10-19T09:00:00+03:00") — RFC 3339.
  • Арифметика: data.submissionEnd - duration("72h"), instance.clock < data.submissionEnd.

Строки, списки, макросы

  • Строки: lowerAscii, split, join, replace, substring и другие функции расширения strings.
  • Списки: size, in, slice, flatten, sort, distinct.
  • Макросы: all, exists, map, filter: size(data.history.filter(n, n.kind == 'lesson')) > 0.
  • Необязательные значения: .?поле, orValue(…).
  • cel.bind(имя, значение, выражение) — локальное имя.

Чего нет

  • Текущего времени. Функции now() нет, её вызов — ошибка типа с подсказкой. Время входит в выражение только как instance.clock и event.time — их задаёт вход движка. Поэтому одно выражение на одних входах даёт одно значение в живом прогоне, тесте и replay.
  • Случайности, ввода-вывода, обращений к памяти и каталогу. База знаний попадает в выражения только через записанный ответ recall, календарь — версией, записанной в журнале.
  • Побочных эффектов. Запись в данные — только set и output.as.
  • Шаблонов {{…}}. Они остаются у правил вывода работы и правил уведомлений; в процессах их нет.

Лимиты

Лимит Значение Когда проверяется
длина выражения 4000 символов схема каталога
глубина дерева выражения 32 при публикации (expression_too_complex)
вложенность итерирующих макросов 3 при публикации (expression_too_complex)
стоимость вычисления 10 000 единиц перед вычислением, по размерам входов (expression_cost_exceeded)

Стоимость оценивается сверху до вычисления: шаг на сегмент пути, вызов функции, итерации макросов по размеру списка. Выше лимита вычисление не запускается: шаг получает ошибку expression_cost_exceeded, её ловит try, иначе экземпляр переходит в failed с понятной причиной — процесс не зависает молча.

Ошибки

Ошибки разбора приходят находками проверки с местом в файле и позицией в выражении:

{"code": "expression_type_error", "severity": "error",
 "path": "/spec/stages/0/steps/1/human/due/at", "file": "processes/tender.yaml",
 "line": 31, "message": "no such field 'submisionDeadline' at 1:34",
 "hint": "data.procurement has submissionDeadline"}
Код Когда
expression_syntax_error синтаксис
expression_type_error тип: неизвестное поле, now(), не тот тип результата по месту (ждали bool, timestamp или duration, список)
expression_too_complex глубина или вложенность макросов
expression_error при вычислении: null, отсутствующее поле, нет календаря
expression_cost_exceeded превышен лимит стоимости

Частые ошибки

Ошибка Правильно
title: 'Счёт ' + data.number — YAML съел кавычки, CEL видит Счёт без кавычек title: "'Счёт ' + data.number"
when: data.note != '' на необязательном поле when: data.?note.orValue('') != ''
due: {at: "now() + duration('P1D')"} due: P1D или {at: "cal.addWorkdays(instance.clock, 1)"}
set: {total: data.amount * 1.2} при amount: {type: integer} в CEL int и double не смешиваются: double(data.amount) * 1.2
data.?approval в конце выражения результат не может остаться optional: data.?approval.orValue('')
сторож стадии читает результат шага сторожа стадий и вехи читают data, stage и milestone: запишите результат шага в данные через output.as
булево значение строкой set: {done: "yes"} set: {done: "true"} — строка YAML с выражением true

Перевод прежних синтаксисов

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

python3 tools/cp_packages.py migrate-expr --package packages/<пакет>          # diff, ничего не пишет
python3 tools/cp_packages.py migrate-expr --package packages/<пакет> --write  # записать
  • Команда печатает diff по файлам; --write записывает с сохранением файла (комментарии, порядок ключей и стиль остаются).
  • Выражения ищет и переводит ядро: нужен control-plane с профилем CEL в PYTHONPATH (или интерпретатор его uv-окружения).
  • Процессы и календари уже на CEL и командой не трогаются.
  • То, что не переводится, печатается с причиной и остаётся как было; если перевод читает переменные сверх профиля (например spawnedBy), это тоже печатается.
Прежнее CEL
{"var": "payload.data.repo"} event.payload.?data.?repo.orValue(null)
{"exists": "payload.data.url"} event.payload.?data.?url.orValue(null) != null
{"lt": [{"var": "x"}, 3]} x != null && x < 3
$.task.customFields.branch task.customFields.?branch.orValue(null)
$.task.customFields.branch! task.customFields.branch (нет поля — ошибка)
$.task.artifact[commit].metadata.sha task.artifacts.?commit.?metadata.?sha.orValue(null)
$.invocation.output.x step.result.?x.orValue(null)
шаблон "Merge $.task.publicId!: $.task.title" конкатенация; отсутствующее — ""
execution.inputs: $.customFields.url task.customFields.?url.orValue(null)
from: "$.customFields.okpdCodes" task.customFields.?okpdCodes.orValue(null)

Где перевод расходится с прежним вычислением

0 в when прежде считался невыполненным условием, в CEL — выполненным; нестроковое значение в шаблоне печатается true, а не True; обязательное ! на поле с пустой строкой прежде давало отказ, в CEL — "". Проверьте такие места после перевода.

См. также