Edge Rules
Edge Rules — это правила окружения, которые ONREZA исполняет на edge до основного route приложения. Через них настраивают redirects, rewrites, headers, cache, deny/log, rate limit и привязку ONREZA Functions route pipeline.
Пользовательские правила описываются в onreza.rules.toml рядом с
onreza.toml или публикуются через UI. Для каждого окружения активен один
effective ruleset: платформа собирает его из пользовательских правил и
автоматических правил адаптера, если адаптеру нужно отразить routing-фичи
фреймворка.
Если логика требует runtime-состояния, внешнего fetch, KV, вычислений,
персонализации или изменения body, это не native Edge Rule. Такую логику
пишите как ONREZA Function route pipeline, а
в Edge Rules оставляйте условие маршрутизации.
Что где настраивается
Заголовок раздела «Что где настраивается»| Поверхность | Что описывает | Где применяется |
|---|---|---|
| Build Output Manifest | Static/compute routes приложения | После Edge Rules |
| ONREZA Functions | HTTP handlers из *.nrz-fn.* файлов |
В route pipeline как step или terminal function |
onreza.rules.toml |
Статические правила, cache policy и route pipeline wiring | До terminal route или вокруг него |
onreza.rules.toml не исполняет код при publish. Файл парсится как TOML,
валидируется как данные и нормализуется в runtime ruleset.
Формат файла
Заголовок раздела «Формат файла»Файл лежит в корне проекта:
my-app/├── onreza.toml├── onreza.rules.toml└── functions/Минимальный файл:
schema = "EDGE_RULE_SET_V1"
[[rule]]id = "redirect-old-docs"when.path = { prefix = "/old-docs" }action.redirect = { target = "/docs", status_code = 308, if_no_file = false }Основные поля:
| Поле | Описание |
|---|---|
schema или schemaVersion |
Версия контракта. Сейчас EDGE_RULE_SET_V1. |
[[rule]] или [[rules]] |
Упорядоченный список правил. Порядок в файле является порядком исполнения. |
id |
Стабильный уникальный ID правила. |
name |
Необязательное человекочитаемое имя. |
enabled |
Необязательный флаг. По умолчанию true. |
when или condition |
Условия срабатывания. Без условий правило матчится на все запросы. |
action.<kind> или action = { type = "..." } |
Ровно одно действие. |
position писать нельзя. Платформа выставляет его сама по порядку [[rule]].
TOML 1.1 authoring profile
Заголовок раздела «TOML 1.1 authoring profile»nrz принимает TOML 1.1 authoring profile. Это значит, что можно писать
читаемый TOML, а CLI перед отправкой нормализует его в canonical contract.
Поддерживается:
schemaвместоschemaVersion;[[rule]]вместо[[rules]];whenвместоcondition;action.redirect,action.cache,action.pipelineи другиеaction.<kind>вместоaction = { type = "..." };- snake_case для полей:
status_code,if_no_file,ttl_seconds,swr_seconds,window_seconds,inherit_gate,cache_position; - TOML 1.1 multi-line inline tables для крупных
pipelineи cache rules; - обычный canonical camelCase формат тоже остаётся валидным.
Ключи внутри headers, query и cookies считаются пользовательскими данными
и не меняются. Для HTTP headers с дефисами используйте кавычки:
action.set_headers = { headers = { "x-frame-options" = "DENY", "x-content-type-options" = "nosniff", },}JSON Schema доступна по адресу:
/schemas/onreza-rules-v1.schema.json.
Локальная проверка:
nrz functions checkКоманда валидирует функции и onreza.rules.toml. Для rules-only проекта она
тоже полезна: неверный TOML, неизвестные поля, дубли id, небезопасный cache
vary и некорректный pipeline будут отклонены до deploy.
Полный пример
Заголовок раздела «Полный пример»schema = "EDGE_RULE_SET_V1"
[actions.security_headers.set_headers]headers = { "x-frame-options" = "DENY", "x-content-type-options" = "nosniff", "referrer-policy" = "strict-origin-when-cross-origin",}
[[rule]]id = "redirect-legacy-docs"when.path = { prefix = "/old-docs" }action.redirect = { target = "/docs", status_code = 308, if_no_file = false }
[[rule]]id = "cache-public-assets"when.path = { glob = "/assets/*" }when.methods = ["GET", "HEAD"]action.cache = { ttl_seconds = 86400, swr_seconds = 600 }
[[rule]]id = "mobile-download-page"when.path = { exact = "/download" }when.device = "mobile"action.rewrite = { target = "/download/mobile.html", if_no_file = false }
[[rule]]id = "dashboard-auth"when.path = { prefix = "/dashboard" }action.pipeline = { inherit_gate = true, steps = [ { use = "require-session", mode = "request", failure = "closed" }, { handle = "@app" }, { use = "add-user-header", mode = "response", failure = "open" }, ],}
[[rule]]id = "api-rate-limit"when.path = { prefix = "/api/" }action.rate_limit = { limit = 120, window_seconds = 60, key = "ip_path" }
[[rule]]id = "deny-blocked-network"when.any = [ { geo = ["KP"] }, { asn = [13335] },]action.deny = { status_code = 403 }
[[rule]]id = "security-headers"action.use = "security_headers"[actions.<name>.<kind>] — необязательный способ переиспользовать одно и то же
действие. В примере security_headers подключается через action.use.
Условия
Заголовок раздела «Условия»Если в одном правиле указано несколько условий, они объединяются через И: правило сработает только когда совпали все заданные поля.
| Condition | Authoring пример | Что проверяет |
|---|---|---|
path |
{ exact = "/docs" } |
Точный путь |
path |
{ prefix = "/admin" } |
Префикс пути |
path |
{ glob = "/blog/*" } |
Glob по пути. * может покрывать вложенные сегменты. Поддерживает захваты {name} (см. Захваты пути). |
methods |
["GET", "HEAD"] |
HTTP methods |
host |
"app.example.com" или "пример.рф" |
Hostname запроса без scheme, port, path и userinfo. IDN-домены можно писать Unicode-строкой; платформа нормализует их в punycode. |
headers |
{ "x-plan" = "pro" } |
Request headers |
query |
{ preview = "1" } |
Query string |
cookies |
{ bucket = "b" } |
Cookies |
geo |
["RU", "KZ"] |
ISO country codes |
asn |
[13335, 15169] |
Autonomous System Number |
device |
"mobile" |
desktop, mobile, tablet, bot |
sourceIpCidrs |
["203.0.113.0/24"] |
Client IP/CIDR |
Canonical форма path тоже валидна:
condition.path = { type = "prefix", value = "/docs" }Через onreza.rules.toml сейчас поддерживаются только exact, prefix и
glob. Regex path matchers не являются публичной authoring-поверхностью.
any и not
Заголовок раздела «any и not»Для ограниченной булевой логики есть два поля:
when.any— список альтернатив через ИЛИ;when.not— отрицание одного вложенного условия.
Внутри any и not доступны те же базовые matchers, но без вложенных any и
not.
[[rule]]id = "deny-datacenter-or-blocked-geo"when.any = [ { geo = ["KP"] }, { asn = [13335] },]action.deny = {}
[[rule]]id = "cache-everything-except-api"when.path = { prefix = "/" }when.not = { path = { prefix = "/api" } }action.cache = { ttl_seconds = 300 }Действия
Заголовок раздела «Действия»| Action | Терминальное | Основные поля | Что делает |
|---|---|---|---|
allow |
Нет | - | Явно продолжает цепочку правил. |
log |
Нет | - | Отмечает совпадение правила. |
deny |
Да в enforce, нет в shadow |
status_code, mode |
Возвращает отказ. По умолчанию 403. |
redirect |
Да | target, status_code, if_no_file |
Возвращает 301, 302, 307 или 308. |
rewrite |
External — да, internal — нет | target, external, if_no_file |
Переписывает путь или проксирует external origin. |
set_headers |
Нет | headers |
Добавляет или заменяет response headers. |
remove_headers |
Нет | headers |
Удаляет response headers. |
cache |
Нет | ttl_seconds, swr_seconds, vary |
Задаёт cache policy downstream response. |
bypass_cache |
Нет | - | Отключает cache для совпавшего downstream route. |
rate_limit |
Да в enforce, нет в shadow |
limit, window_seconds, key, mode |
Ограничивает частоту запросов. |
pipeline |
Function terminal — да, @app — нет |
steps, override, inherit_gate |
Запускает ONREZA Functions вокруг route. |
Терминальное действие завершает обработку ruleset. Нетерминальные действия накапливают эффекты и позволяют следующим правилам продолжить обработку.
[[rule]]id = "deny-admin-posts"when.path = { prefix = "/admin" }when.methods = ["POST"]action.deny = { status_code = 403 }mode = "shadow" позволяет проверить правило без блокировки запроса:
action.deny = { status_code = 451, mode = "shadow" }redirect и rewrite
Заголовок раздела «redirect и rewrite»[[rule]]id = "redirect-slug"when.path = { exact = "/pricing-old" }action.redirect = { target = "/pricing", status_code = 301 }
[[rule]]id = "redirect-cyrillic-domain"when.path = { glob = "/{rest...}" }when.any = [ { host = "пример.рф" }, { host = "www.пример.рф" },]action.redirect = { target = "https://example.com/{rest}", status_code = 301, if_no_file = false,}
[[rule]]id = "rewrite-legacy"when.path = { prefix = "/legacy" }action.rewrite = { target = "/modern", if_no_file = false }
[[rule]]id = "proxy-docs"when.path = { prefix = "/external-docs" }action.rewrite = { target = "https://docs.example.com/", external = true, if_no_file = false,}if_no_file = true означает file-priority поведение: если matching static файл
существует, он имеет приоритет и правило не применяется. if_no_file = false
применяет правило сразу.
Redirect принимает относительный путь или absolute http/https URL. Это
обычный HTTP redirect: edge вернёт Location, но не будет проксировать внешний
origin. IDN-домены в host и absolute URL можно писать кириллицей — при publish
они нормализуются в punycode.
Internal rewrite принимает относительный путь. External rewrite принимает только
absolute https URL и проксирует запрос к внешнему origin. Запросы к
приватным/internal адресам блокируются.
headers
Заголовок раздела «headers»[[rule]]id = "security-headers"action.set_headers = { headers = { "x-frame-options" = "DENY", "x-content-type-options" = "nosniff", },}
[[rule]]id = "remove-debug"when.path = { prefix = "/public" }action.remove_headers = { headers = ["x-debug"] }Порядок правил важен: поздний set_headers может задать финальное значение, а
поздний remove_headers удаляет ранее выставленный header с тем же именем.
cache и bypass_cache
Заголовок раздела «cache и bypass_cache»[[rule]]id = "cache-public"when.path = { prefix = "/public" }action.cache = { ttl_seconds = 600, swr_seconds = 60 }
[[rule]]id = "never-cache-preview"when.query = { preview = "1" }action.bypass_cache = {}Если cache зависит от request-specific условий, обязательно укажите vary.
Валидатор не даст опубликовать правило, которое может смешать ответы разных
пользователей.
| Условие в cache rule | Нужный vary |
|---|---|
headers |
"header" |
query |
"query" |
cookies |
"cookie" |
geo |
"geo" |
asn |
"asn" |
device |
"device" |
Пример:
[[rule]]id = "cache-mobile-plan-page"when.path = { exact = "/landing" }when.headers = { "x-plan" = "pro" }when.device = "mobile"action.cache = { ttl_seconds = 300, vary = ["header", "device"],}rate_limit
Заголовок раздела «rate_limit»[[rule]]id = "api-rate-limit"when.path = { prefix = "/api/" }action.rate_limit = { limit = 120, window_seconds = 60, key = "ip_path",}Поддерживаемые ключи:
| Key | Scope |
|---|---|
ip |
Один client IP |
ip_path |
Client IP + path |
ip_host |
Client IP + host |
host |
Host, общий для всех клиентов этого host |
mode = "shadow" считает совпадения, но не блокирует запросы.
pipeline
Заголовок раздела «pipeline»Pipeline запускает ONREZA Functions вокруг downstream route.
[[rule]]id = "dashboard-auth"when.path = { prefix = "/dashboard" }action.pipeline = { inherit_gate = true, steps = [ { use = "require-session", mode = "request", failure = "closed" }, { handle = "@app" }, { use = "add-user-header", mode = "response", failure = "open" }, { use = "audit-access", mode = "observe", failure = "open" }, ],}Pipeline состоит из steps:
| Step | Где ставить | Что делает |
|---|---|---|
{ use = "fn", mode = "request" } |
До handle |
Может вернуть новый Request или короткозамкнуть Response. |
{ handle = "@app" } |
Ровно один terminal handle | Продолжает в обычный static/compute/app route. |
{ handle = "fn" } |
Ровно один terminal handle | Делает функцию terminal route. Нужен override = true. |
{ use = "fn", mode = "response" } |
После handle |
Может изменить downstream response. |
{ use = "fn", mode = "observe" } |
После handle |
Side-effect step после downstream response. |
Правила pipeline:
- должен быть ровно один
handle; requeststeps идут доhandle;responseиobservesteps идут послеhandle;handle = "@app"оборачивает обычный app route и не требуетoverride;handle = "function-name"делает функцию terminal route и требуетoverride = true;failure = "closed"останавливает pipeline при ошибке step;failure = "open"пропускает упавший step и продолжает;- если
failureне указан,requeststep по умолчаниюclosed, аresponse/observestep по умолчаниюopen; cache_position = "before"или"after"задаёт, по какую сторону cache boundary выполняется request step. По умолчанию используетсяbefore.
Function terminal пример:
[[rule]]id = "api-function"when.path = { prefix = "/api/" }action.pipeline = { override = true, steps = [ { handle = "api" }, ],}Захваты пути и интерполяция
Заголовок раздела «Захваты пути и интерполяция»Glob-условие умеет запоминать части пути и переносить их в target правила
redirect/rewrite или в значения set_headers. Это позволяет описывать
параметрические правила одной строкой, без отдельной функции.
В glob-пути доступны два вида захвата:
{name}— один сегмент пути (до/);{name...}— остаток пути целиком, включая/.
В target и в значениях заголовков захват подставляется через {name} — в обоих
случаях по имени, без ...:
# /blog/hello-world → /posts/hello-world.html[[rule]]id = "clean-urls"when.path = { glob = "/blog/{slug}" }action.rewrite = { target = "/posts/{slug}.html" }
# /docs/guide/intro → редирект на /guides/guide/intro[[rule]]id = "move-docs"when.path = { glob = "/docs/{rest...}" }action.redirect = { target = "/guides/{rest}", status_code = 308 }
# междоменный редирект с сохранением пути[[rule]]id = "move-domain"when.path = { glob = "/{rest...}" }action.redirect = { target = "https://example.com/{rest}", status_code = 301 }
# проброс части пути во внешний адрес[[rule]]id = "sitemap-proxy"when.path = { glob = "/sitemap-{id}.xml" }action.rewrite = { target = "https://origin.example.com/v1/sitemap?path=/sitemap-{id}.xml", external = true,}
# захват можно подставить и в заголовок[[rule]]id = "tag-tenant"when.path = { glob = "/t/{tenant}/{rest...}" }action.set_headers = { headers = { "x-tenant" = "{tenant}" } }Правила захватов:
- захваты работают только в
glob(вexactиprefixфигурные скобки — обычные символы); - захваты объявляются только в основном
when.path; glob-пути внутри ветокany/notобъявлять захваты не могут; - имена захватов уникальны в пределах правила, splat
{name...}— не более одного; - каждый
{name}вtarget/заголовке должен быть объявлен в пути этого же правила, splat подставляется по имени без...— иначе правило не пройдёт публикацию; - чтобы вывести литеральный
{name}без подстановки (например, URI-template в заголовке), удвойте скобки:{{name}}превращается в текст{name}.
Порядок запроса
Заголовок раздела «Порядок запроса»Упрощённый порядок обработки:
access protection-> ordered Edge Rules-> request pipeline steps-> cache lookup-> terminal route: static / function / compute-> response and observe pipeline steps-> response headersКлючевые правила:
- правила идут сверху вниз;
- терминальное действие завершает обработку ruleset;
cache,bypass_cache,set_headers,remove_headers,allow,logи@apppipeline продолжают цепочку;- если несколько non-terminal правил меняют один эффект, позднее правило задаёт финальное состояние;
- preview и production имеют независимые rulesets.
CLI и UI
Заголовок раздела «CLI и UI»Для каждого окружения активен один ruleset. Последняя успешная активация окружения выигрывает.
| Сценарий | Итог |
|---|---|
В HEAD есть onreza.rules.toml, вы делаете успешный nrz deploy |
Пользовательские rules из репозитория становятся активными для target environment |
В HEAD нет onreza.rules.toml, вы делаете успешный nrz deploy |
Текущие пользовательские rules сохраняются; адаптер может обновить только свои автоматические rules |
| После этого вы публикуете rules через UI | UI обновляет пользовательские rules для этого environment |
Потом снова проходит deploy с onreza.rules.toml |
Repo rules снова обновляют пользовательскую часть |
Standalone functions publish без onreza.rules.toml |
Текущие пользовательские rules сохраняются |
Publish с пустым rules = [] |
Пользовательские rules явно очищаются; автоматические adapter rules могут остаться |
Rollback переключает окружение назад вместе с app artifact, function revisions и Edge Rules.
Когда выбрать ONREZA Functions
Заголовок раздела «Когда выбрать ONREZA Functions»Не пытайтесь выразить в onreza.rules.toml то, что зависит от runtime-данных:
- lookup session/user в KV;
- запрос во внешний API;
- A/B с вычислением bucket на лету;
- per-user redirects;
- auth/RBAC;
- изменение response body;
- логика, требующая циклов, даты, случайности или состояния.
Для этого создайте ONREZA Function и привяжите её через pipeline. Path
condition правила всё равно применяется нативно, а JavaScript выполнится только
на совпавших маршрутах.
Частые ошибки
Заголовок раздела «Частые ошибки»| Ошибка | Как исправить |
|---|---|
Добавили position в правило |
Удалите поле. Порядок берётся из файла. |
Использовали regex path |
Используйте exact, prefix или glob. |
Cache rule зависит от headers, query, cookies, geo, asn или device, но нет vary |
Добавьте соответствующие значения в action.cache.vary. |
Pipeline содержит два handle |
Оставьте ровно один terminal handle. |
request step стоит после handle |
Перенесите его до handle. |
Function terminal без override = true |
Добавьте override = true или используйте handle = "@app". |
| Header с дефисом написан без кавычек | Пишите "x-frame-options" = "DENY". |