Приложение B. Что изменено ради согласованности
При переводе черновика в стандарт найдены и устранены противоречия. Таблица — для сверки со старыми обсуждениями.
| Было | Стало | Причина |
|---|---|---|
data Dog { use Animal } | embed Animal | use уже означает импорт |
service Users @v2 {, атрибуты после имени | @v(2) service Users { — атрибуты всегда перед объявлением | один синтаксис атрибутов |
resize @gpu в пайплайне | resize on gpu | @ зарезервирован за атрибутами |
2026-11-03T18:00 @ "Europe/Moscow" | 2026-11-03T18:00.in(tz"Europe/Moscow") | та же причина; @ убран из символов пользовательских операторов |
тернарный a > b ? a : b | if a > b { a } else { b } | конфликт с постфиксным ? и T? |
поле email?: Email | email: Email? | один способ записать опционал |
@auth(role: Admin) и политики | только политики и сокращение @allow(…) | два механизма для одного |
@secret apiKey: str | apiKey: secret<str> | один механизм секретов |
config<App>(), dynamic<Limits>() | App.get(), Limits.get() | config — ключевое слово |
every(5m) -> sync() | every(5m) { sync() } | -> — только цепочки |
select { rx => j -> process(j) } | select { recv j from rx => process(j) } | та же причина |
.map @local(…) в cluster.shards | .map(…) — всегда на владельцах шардов | лишняя конструкция |
stream<T> resumable | @resumable rpc … | атрибуты вместо модификаторов типа |
-> u => (a(u), b(u)) как fan-out | шаг-кортеж -> (a(it), b(it)) | лямбда, возвращающая кортеж, — обычное значение |
@retry на любом rpc | только с @idempotent или @readonly | повтор неидемпотентного вызова опасен |
keep x для выноса из арены | арена — оптимизация реализации, без синтаксиса | семантика не должна зависеть от аллокатора |
| traits и интерфейсы | только интерфейсы; служебные — @explicit | структурное соответствие + защита Drop/Copy от случайной реализации |
| неявное переопределение в embed | open fn в базе + override fn | однозначная диспетчеризация |
| теги полей по порядку объявления | oxl.schema.lock + @tag(n) | вставка поля не ломает схему |
retry в STM | check(cond) | конфликт с суффиксом цепочки retry |
завершение main останавливает узел | узел работает, пока что-то размещает | main — задача запуска |
cluster.leader блокирует | регистрирует задачу и возвращается | вызывается из main на всех узлах |
serves для топиков | топикам нужна только роль с journal | топики живут в хранилище |
литералы Money.zero | Money.zero() | так было до ассоциированных констант; теперь допустимо и Money.ZERO (§4.5) |
| строгая обязательность политики для всех rpc | внешним принципалам — только явное; @internal; ошибка лишь для недоступного никому rpc | внутренние rpc разрешаются графом вызовов |
| таблицы во встроенном хранилище поверх LSM | таблицы builtin:// — SQLite на реплику шарда, Raft реплицирует наборы изменений строк; LSM — для KV и системных данных | собственный исполнитель запросов (мини-СУБД) слишком сложен и рискован; SQLite даёт полный проверенный SQL |
checkout в V.5 очищал корзину в фоне (~> Cart(uid).clear()) | checkout ждёт Cart(uid).removeOrdered(n); в фон уходит только ~> recs.learn(…) | исправлено в примере V.5: фоновая очистка гонялась со следующим запросом пользователя (могла стереть позиции, добавленные после снимка); ~> годится для задач, не влияющих на состояние, от которого зависит ответ |
inventory.reserve(ReserveReq{ items: snap.items }), payments.charge(ChargeReq{ … }), recs.learn(LearnReq{ … }) в V.5 | inventory.reserve(items: snap.items), payments.charge(user: …, amount: …), recs.learn(user: …, items: …) | исправлено в примере V.5: при import proto сообщение-запрос раскрывается в параметры rpc (§16.3), конструировать его в вызове не нужно |
| в V.5 не было A/B-теста: §18.5 и критерий приёмки ссылаются на «search v2», которого нет в примере | в V.5 добавлены Shop.search, search/search.oxl (варианты searchByName / searchByTag, выбор через ab.variant(..)), test ab "search v2" и записанный трафик traffic/search.oxlrec; ab.variant(..) из std.ab возвращает имя варианта запроса | исправлено в примере V.5 (ответ заказчика 1a): пример показывает test ab и oxl ab на настоящем сервисе; способ узнать вариант из кода в §18.5 не был задан (ответ 8a: динамический Flag по split by) |
в V.5 шаг после respond it.id получал p (фото целиком) | шаг после respond it.id получает PhotoId: id => makeThumbs(id, THUMB_SIZES); владелец для PhotoReady — параметр пайплайна owner | исправлено в примере V.5: по тексту §10.6 следующему шагу передаётся значение respond |
| вопрос об асинхронных деструкторах | синхронный Drop + явный close() с defer + @must_close | неявные точки приостановки, конфликт с отменой и потеря ошибок перевешивают удобство |
строки let/assert на верхнем уровне файла в примерах (§4.2, V.3, приложение A); тело test ab как блок | верхний уровень — только объявления; такие строки — фрагменты внутри функций и test; тело test ab — набор предложений variant/split/metric/guard/traffic (§0.2, §18.5) | уточнено в редакции 1.0 (D-8052; DIV-001): парсер принимает на верхнем уровне только элементы |
ProtocolError.Internal / .InvalidArgument как варианты перечисления | ProtocolError — @error data { msg: str, code: i32 } с ассоциированными константами Internal (13), InvalidArgument (3), Unimplemented (12); обе записи работают (§6.5) | уточнено в редакции 1.0 (D-8052; DIV-004; D-2901) |
TxConflict — ошибка результата atomic(db) | atomic(db) сохраняет тип тела; после исчерпания повторов — паника TxConflict (§11.2) | поправка редакции 1.0 (D-8052; DIV-012): тип результата не меняется ради редкого случая под нагрузкой |
[lint] показан только значением error | допустимы error, warn/warning, off/allow (III.2) | поправка редакции 1.0 (D-8056; DIV-042) |
@owner ограничивает все чтения и записи | условие владельца добавляется к find/query/count/update/delete, не к load(…) и сырому SQL; чужие записи отвергаются (§13.4); условия set динамической конфигурации — только pure | поправка редакции 1.0 (D-8056; DIV-028, DIV-029) |
политики debug и inspect названы без правил | заданы: роль Admin, debug/inspect или сертификат кластера с ролью (III.7, III.8) | поправка редакции 1.0 (D-8056; DIV-041) |
Format, Encoder, Decoder названы без сигнатур; decode(b, MyFormat) | сигнатуры описаны (§9.5); декодирование собственным форматом — необязательная возможность impl-user-format-decode | поправка редакции 1.0 (D-8053; DIV-007) |
V.5: типы import proto общие для модулей, Client<Recs> в dep, reserve(items: …) | реализация примера (examples/shop): типы .proto приватны для checkout, в тестах — алиасы OutOfStockError/DeclinedError; ReserveResp/ChargeResp имеют второе поле; Money на проводе — int64; ключ payments не передаётся (Connect без auth:) | пример V.5 информативен; расхождения собраны в examples/shop/DEVIATIONS.md (#1; D-8057; DIV-047) |
V.5: UserId(ctx.userId()?), caller.isUser(id) с UserId | Ctx.userId() и isUser принимают str (JWT sub); callerId(ctx) разбирает его в UserId (§13.2, impl-principal-id) | D-8057; DIV-049; D-2905 |
V.5: OrderEvents … sharded by .userId | топик не шардирован (у элемента enum нет поля userId); watch фильтрует по владельцу | D-8057; DIV-050 (DEVIATIONS #5) |
V.5: photos с Image из imagekit, BlobStore, makeThumbs/moderate в photos/ml.oxl | imagekit и BlobStore в 1.0 нет: образ — bytes, Blobs — локальный актор, makeThumbs/moderate — заглушки; THUMB_SIZES: List<u32>; Db.open(…)! | D-8057; DIV-051 (DEVIATIONS #6, #10, #11); impl-stdlib-optional |
V.5: тесты asUser, mock Inventory {…}, test sim, test integ with cluster(nodes: 3), asUser(uid) { a -> b }? | asUser и TEST_ITEM — в tests/fixtures.oxl; ? в блоке asUser заменён на !; цепочку ждёт явный await (a -> b); в sim — 6 логических узлов (по роли, для saga — 2) | D-8057; DIV-052 (DEVIATIONS #7; D-6114, D-6252) |
V.5: поля pub(api) data без pub | pub(api) нужен на каждом поле, которое использует другой модуль (поля приватны по умолчанию, §4.2) | D-8057; DIV-053 (DEVIATIONS #9) |
V.5: expose 443 tls(acme: …), seeds k8s(…) | в тексте примера оставлено как есть; при приёмке переопределено флагами и переменными (--tls-cert/--tls-key, static[…], локальный JWKS, OXL_SHARDS=16, фиктивные gRPC-серверы) | окружение: impl-acme, impl-seeds-k8s (D-8057; DIV-054) |
atomic(db, cross_shard: true) разрешает несколько шардов ценой двухфазного коммита | параметр принимается с предупреждением cross-shard-tx, тело выполняется как обычная транзакция; выход за один шард — CrossShardTx во время работы (§11.2, impl-2pc) | двухфазный коммит вне 1.0 (D-8064; D-7790) |
@allow(…) — только краткая политика доступа | добавлена форма @allow(lint: правило, reason: "…") — подавление правила анализатора на объявлении (III.4) | идея C11 (D-8064; D-7830) |
о dbg и о assert в профиле prod Стандарт молчал | dbg вырезается в prod (ключ профиля dbg), assert выполняется во всех профилях (§2.2, §18.2) | единообразие профилей (D-8064; D-7800) |
| исходящие вызовы несут пользователя и сервис без исключений (§13.2) | вызовы внешних сервисов (S.connect) не несут x-oxl-principal/x-oxl-user (§13.2) | утечка идентичности пользователя во внешние системы, дефект 1-й критичности (D-8066; D-8300) |
Исходник: docs/spec/oxl-standard.md