Appendix B. What was changed for consistency
When the draft was turned into the standard, contradictions were found and eliminated. The table is for reconciliation with old discussions.
| Was | Became | Reason |
|---|---|---|
data Dog { use Animal } | embed Animal | use already means import |
service Users @v2 {, attributes after the name | @v(2) service Users { — attributes always before the declaration | one attribute syntax |
resize @gpu in a pipeline | resize on gpu | @ is reserved for attributes |
2026-11-03T18:00 @ "Europe/Moscow" | 2026-11-03T18:00.in(tz"Europe/Moscow") | the same reason; @ removed from the symbols of user-defined operators |
ternary a > b ? a : b | if a > b { a } else { b } | conflict with the postfix ? and T? |
field email?: Email | email: Email? | one way to write an optional |
@auth(role: Admin) and policies | only policies and the shorthand @allow(…) | two mechanisms for one thing |
@secret apiKey: str | apiKey: secret<str> | one mechanism for secrets |
config<App>(), dynamic<Limits>() | App.get(), Limits.get() | config is a keyword |
every(5m) -> sync() | every(5m) { sync() } | -> is only for chains |
select { rx => j -> process(j) } | select { recv j from rx => process(j) } | the same reason |
.map @local(…) in cluster.shards | .map(…) — always on the shard owners | a superfluous construct |
stream<T> resumable | @resumable rpc … | attributes instead of type modifiers |
-> u => (a(u), b(u)) as fan-out | the tuple step -> (a(it), b(it)) | a lambda returning a tuple is an ordinary value |
@retry on any rpc | only with @idempotent or @readonly | repeating a non-idempotent call is dangerous |
keep x for taking a value out of an arena | the arena is an implementation optimisation, without syntax | semantics must not depend on the allocator |
| traits and interfaces | only interfaces; service ones are @explicit | structural conformance + protection of Drop/Copy from accidental implementation |
| implicit override in embed | open fn in the base + override fn | unambiguous dispatch |
| field tags by declaration order | oxl.schema.lock + @tag(n) | inserting a field does not break the schema |
retry in STM | check(cond) | conflict with the chain suffix retry |
terminating main stops the node | the node runs while it hosts something | main is a start-up task |
cluster.leader blocks | registers a task and returns | it is called from main on all nodes |
serves for topics | topics need only a role with journal | topics live in the storage |
literals Money.zero | Money.zero() | this was so before associated constants; now Money.ZERO is allowed too (§4.5) |
| strict mandatory policy for all rpcs | for external principals only the explicit; @internal; an error only for an rpc inaccessible to anyone | internal rpcs are resolved by the call graph |
| tables in the built-in storage on top of an LSM | builtin:// tables are SQLite per shard replica, Raft replicates sets of row changes; the LSM is for KV and system data | a custom query executor (a mini-DBMS) is too complex and risky; SQLite gives complete, proven SQL |
in V.5 checkout cleared the cart in the background (~> Cart(uid).clear()) | checkout waits for Cart(uid).removeOrdered(n); only ~> recs.learn(…) goes to the background | fixed in example V.5: the background cleanup raced with the user's next request (it could erase items added after the snapshot); ~> is suitable for tasks that do not affect the state the response depends on |
inventory.reserve(ReserveReq{ items: snap.items }), payments.charge(ChargeReq{ … }), recs.learn(LearnReq{ … }) in V.5 | inventory.reserve(items: snap.items), payments.charge(user: …, amount: …), recs.learn(user: …, items: …) | fixed in example V.5: with import proto the request message is expanded into rpc parameters (§16.3), it need not be constructed in the call |
| V.5 had no A/B test: §18.5 and the acceptance criterion refer to "search v2", which is not in the example | V.5 gained Shop.search, search/search.oxl (variants searchByName / searchByTag, chosen via ab.variant(..)), test ab "search v2" and the recorded traffic traffic/search.oxlrec; ab.variant(..) from std.ab returns the variant name of a request | fixed in example V.5 (customer answer 1a): the example shows test ab and oxl ab on a real service; the way to learn the variant from code was not defined in §18.5 (answer 8a: a dynamic Flag by split by) |
in V.5 the step after respond it.id received p (the whole photo) | the step after respond it.id receives a PhotoId: id => makeThumbs(id, THUMB_SIZES); the owner for PhotoReady is the pipeline parameter owner | fixed in example V.5: per the text of §10.6 the value of respond is passed to the next step |
| the question of asynchronous destructors | synchronous Drop + explicit close() with defer + @must_close | implicit suspension points, conflict with cancellation and loss of errors outweigh the convenience |
top-level let/assert lines in examples (§4.2, V.3, appendix A); the body of test ab as a block | the top level holds declarations only; such lines are fragments inside functions and tests; the body of test ab is a set of clauses variant/split/metric/guard/traffic (§0.2, §18.5) | clarified in edition 1.0 (D-8052; DIV-001): the parser accepts only items at the top level |
ProtocolError.Internal / .InvalidArgument as enum variants | ProtocolError is @error data { msg: str, code: i32 } with the associated constants Internal (13), InvalidArgument (3), Unimplemented (12); both notations work (§6.5) | clarified in edition 1.0 (D-8052; DIV-004; D-2901) |
TxConflict as an error in the result of atomic(db) | atomic(db) keeps the type of the body; after the retries are exhausted — the panic TxConflict (§11.2) | an edition 1.0 correction (D-8052; DIV-012): the result type does not change for a rare case under load |
[lint] shown only with the value error | error, warn/warning, off/allow are allowed (III.2) | an edition 1.0 correction (D-8056; DIV-042) |
@owner restricts all reads and writes | the owner condition is added to find/query/count/update/delete, not to load(…) and raw SQL; foreign writes are rejected (§13.4); the set conditions of the dynamic config are pure only | an edition 1.0 correction (D-8056; DIV-028, DIV-029) |
the policies debug and inspect are named without rules | the rules are given: the role Admin, debug/inspect or a cluster certificate with a role (III.7, III.8) | an edition 1.0 correction (D-8056; DIV-041) |
Format, Encoder, Decoder are named without signatures; decode(b, MyFormat) | the signatures are described (§9.5); decoding with a custom format is the optional feature impl-user-format-decode | an edition 1.0 correction (D-8053; DIV-007) |
V.5: import proto types shared between modules, Client<Recs> in dep, reserve(items: …) | the implementation of the example (examples/shop): .proto types are private to checkout, in tests there are the aliases OutOfStockError/DeclinedError; ReserveResp/ChargeResp have a second field; Money on the wire is int64; the payments key is not passed (Connect without auth:) | example V.5 is informative; the deviations are collected in examples/shop/DEVIATIONS.md (#1; D-8057; DIV-047) |
V.5: UserId(ctx.userId()?), caller.isUser(id) with UserId | Ctx.userId() and isUser accept str (JWT sub); callerId(ctx) parses it into a UserId (§13.2, impl-principal-id) | D-8057; DIV-049; D-2905 |
V.5: OrderEvents … sharded by .userId | the topic is not sharded (an enum element has no field userId); watch filters by owner | D-8057; DIV-050 (DEVIATIONS #5) |
V.5: photos with Image from imagekit, BlobStore, makeThumbs/moderate in photos/ml.oxl | imagekit and BlobStore are not in 1.0: the image is bytes, Blobs is a local actor, makeThumbs/moderate are stubs; THUMB_SIZES: List<u32>; Db.open(…)! | D-8057; DIV-051 (DEVIATIONS #6, #10, #11); impl-stdlib-optional |
V.5: tests asUser, mock Inventory {…}, test sim, test integ with cluster(nodes: 3), asUser(uid) { a -> b }? | asUser and TEST_ITEM are in tests/fixtures.oxl; ? in an asUser block is replaced by !; the chain is awaited explicitly with await (a -> b); the sim has 6 logical nodes (by role, 2 for saga) | D-8057; DIV-052 (DEVIATIONS #7; D-6114, D-6252) |
V.5: pub(api) data fields without pub | pub(api) is needed on every field used by another module (fields are private by default, §4.2) | D-8057; DIV-053 (DEVIATIONS #9) |
V.5: expose 443 tls(acme: …), seeds k8s(…) | left as is in the example text; in acceptance overridden with flags and variables (--tls-cert/--tls-key, static[…], local JWKS, OXL_SHARDS=16, fake gRPC servers) | the environment: impl-acme, impl-seeds-k8s (D-8057; DIV-054) |
atomic(db, cross_shard: true) permits several shards at the cost of a two-phase commit | the parameter is accepted with the warning cross-shard-tx, the body is executed as an ordinary transaction; going beyond one shard is CrossShardTx at run time (§11.2, impl-2pc) | two-phase commit is outside 1.0 (D-8064; D-7790) |
@allow(…) is only a short access policy | the form @allow(lint: rule, reason: "…") is added — suppression of an analyzer rule on a declaration (III.4) | idea C11 (D-8064; D-7830) |
the Standard was silent about dbg and assert in the prod profile | dbg is stripped in prod (profile key dbg), assert is executed in all profiles (§2.2, §18.2) | uniformity of profiles (D-8064; D-7800) |
| outgoing calls carry the user and the service without exceptions (§13.2) | calls of external services (S.connect) do not carry x-oxl-principal/x-oxl-user (§13.2) | a leak of the user's identity to external systems, a criticality 1 defect (D-8066; D-8300) |
Source: docs/spec/oxl-standard.en.md