OxL Reference

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.

WasBecameReason
data Dog { use Animal }embed Animaluse already means import
service Users @v2 {, attributes after the name@v(2) service Users { — attributes always before the declarationone attribute syntax
resize @gpu in a pipelineresize 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 : bif a > b { a } else { b }conflict with the postfix ? and T?
field email?: Emailemail: Email?one way to write an optional
@auth(role: Admin) and policiesonly policies and the shorthand @allow(…)two mechanisms for one thing
@secret apiKey: strapiKey: 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 ownersa superfluous construct
stream<T> resumable@resumable rpc …attributes instead of type modifiers
-> u => (a(u), b(u)) as fan-outthe tuple step -> (a(it), b(it))a lambda returning a tuple is an ordinary value
@retry on any rpconly with @idempotent or @readonlyrepeating a non-idempotent call is dangerous
keep x for taking a value out of an arenathe arena is an implementation optimisation, without syntaxsemantics must not depend on the allocator
traits and interfacesonly interfaces; service ones are @explicitstructural conformance + protection of Drop/Copy from accidental implementation
implicit override in embedopen fn in the base + override fnunambiguous dispatch
field tags by declaration orderoxl.schema.lock + @tag(n)inserting a field does not break the schema
retry in STMcheck(cond)conflict with the chain suffix retry
terminating main stops the nodethe node runs while it hosts somethingmain is a start-up task
cluster.leader blocksregisters a task and returnsit is called from main on all nodes
serves for topicstopics need only a role with journaltopics live in the storage
literals Money.zeroMoney.zero()this was so before associated constants; now Money.ZERO is allowed too (§4.5)
strict mandatory policy for all rpcsfor external principals only the explicit; @internal; an error only for an rpc inaccessible to anyoneinternal rpcs are resolved by the call graph
tables in the built-in storage on top of an LSMbuiltin:// tables are SQLite per shard replica, Raft replicates sets of row changes; the LSM is for KV and system dataa 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 backgroundfixed 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.5inventory.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 exampleV.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 requestfixed 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 ownerfixed in example V.5: per the text of §10.6 the value of respond is passed to the next step
the question of asynchronous destructorssynchronous Drop + explicit close() with defer + @must_closeimplicit 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 blockthe 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 variantsProtocolError 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 errorerror, warn/warning, off/allow are allowed (III.2)an edition 1.0 correction (D-8056; DIV-042)
@owner restricts all reads and writesthe 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 onlyan edition 1.0 correction (D-8056; DIV-028, DIV-029)
the policies debug and inspect are named without rulesthe 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-decodean 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 UserIdCtx.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 .userIdthe topic is not sharded (an enum element has no field userId); watch filters by ownerD-8057; DIV-050 (DEVIATIONS #5)
V.5: photos with Image from imagekit, BlobStore, makeThumbs/moderate in photos/ml.oxlimagekit 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 pubpub(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 committhe 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 policythe 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 profiledbg 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