OxL Reference

DEVIATIONS.md

Deviations of examples/shop from [С V.5]#

The package follows the Standard's "shop" example. Everything below differs from it; each entry says why. State after the stage 4 acceptance (wave 7). Errata of V.5 approved by the customer: request messages of import proto services flattened into rpc parameters (D-5334, no longer a deviation), the next pipeline step gets the respond value (D-5116/D-5334, no longer a deviation), the cart fix (D-5300: checkout waits for removeOrdered, the background step is recs.learn), trace_sample in the cluster block (D-5310). Environment overrides of the acceptance (customer decision 13, not source deviations): --seeds static[…] / oxl dev instead of k8s, --tls-cert/--tls-key instead of ACME, a local JWKS (OXL_AUTH_JWKS) instead of the issuer, OXL_SHARDS=16 instead of 256 shards (customer decision 2), APP_*_ADDR for the three fake gRPC servers (tests/shop/fakes), APP_DB_URL=builtin://shop (the dev profile uses sqlite::memory: for unit tests).

Removed since stage 3: config Features is now dynamic Features; the saga talks to the warehouse / payments through import proto clients (dep inventory: Client<Inventory>, provide … for role saga); the full V.5 cluster block (seeds, auth, defaults, trace_sample, roles api/storage/saga/gpu/ml, assign by env else label else auto); cluster.ids.next(); topics OrderEvents (with watch), StatsUpdates, PhotoEvents; @shard on Order; the leader task and the per-node relay in main; the recommendation model is the Client<Recs> of proto/recs.proto.

#V.5herereasonstatus
1Client<Recs> as a module dep used by checkout; OutOfStock/Declined visible to testsreserve(items: …) takes the proto message Line, charge(user:, amount: snap.total.minor); ReserveResp/ChargeResp carry a second field so that r.reservationId / it.receiptId read as in V.5; dep recs is a dep of impl Shop (module-level dep works only in sagas and pipelines, D-5101); the types of .proto files are private to checkout, tests use the aliases OutOfStockError / DeclinedError; rpc names in the .proto files are lower case as V.5 calls them; Money is int64 on the wire; paymentsKey is not sent (Connect has no auth: parameter); the messages are package-visible since D-6108 (tests write ReserveResp{ … })import proto typeskept (compiler)
2STORAGE_SHARDS: u32STORAGE_SHARDS / ORDER_SHARDS (i64): shards n of a role and @shard(.., count: ORDER_SHARDS) want i64 (the constant in @shard(count:) is evaluated since D-5480; the literal 64 was removed, D-7440)compiler gapskept
3UserId(ctx.userId()?); caller.isUser(id) with a UserIdcallerId(ctx) parses Ctx.userId() (a str, D-2905: the JWT sub) into UserId; AccessDenied if absent or not a number; policies compare the text: caller.isUser("{id}") (users), caller.isUser("{userId}") (Cart, enforced since D-5523)Ctx.userId() / isUser take strkept
4main: leader stats computes computeStats(&db) and emits StatsUpdates; subscribes in a background taskthe leader calls the @internal rpc Orders.publishStats (it owns db) which publishes into the topic StatsUpdates; the per-node subscription is Orders.relayStats() (written when a topic of another module was not importable by name; it is since D-5491)module-level dep db in main is not implementedkept
5Orders.create: atomic(db) { db.insert(o); emit OrderEvents(…) } (outbox); OrderEvents … sharded by .userId; watch = OrderEvents.subscribe(key: ctx.userId()?, from: .Latest)create is the V.5 form again (D-5520/D-5521/D-5524: the acknowledged-but-lost orders of D-5488 came from a transaction leaked by a cancelled handler, plus commits across a leader change); the topic is not sharded (a sharded by .field needs a field of the element, an enum has none); watch filters the stream by the owner of the eventenum element cannot be shardedkept (topic sharding only)
6photos: Image from imagekit, BlobStore, makeThumbs/moderate in photos/ml.oxlthe image is bytes (fewer than four bytes is not an image), Blobs is a local actor per node, makeThumbs / moderate are stubs that print photo-step … node= (the acceptance reads it to prove the hop to the gpu / ml nodes); the owner for PhotoReady is the pipeline parameter; decodePhoto is not pure (cluster.ids)no imagekitkept
7asUser(id) { … } from std.test; mock Inventory {…} / mock Payments {…}; test sim … seeds 1..=300; test integ with cluster(nodes: 3); Shop.addToCart(TEST_ITEM)? inside asUser; asUser(uid) { a -> b }?; node.kill(..) after 50msthe four V.5 tests are in tests/checkout_test.oxl as written, with mock / S.calls (D-6100..D-6105; S.calls(.m) is the List<Call> of the Standard and .count is the length of a List, D-7250) and the integ test on 3 real node processes (D-6110). Differences: asUser and TEST_ITEM (a static … : frozen Item) are tests/fixtures.oxl; ? inside the asUser block is ! (? would return from the block); the chain is awaited explicitly (await (a -> b)); the sim test runs on 6 logical nodes of the simulation (one per role, two for saga; D-6252) for seeds 1..=300 (stage 5 wave 2: services, saga journal and routing of production on simulated nodes, D-6250..D-6262; the membership and the shared storage are simplified, D-6256/D-6257) and writes node.kill("saga-0", after: 50ms) with std.test. qualified; the other saga tests (out of stock, declined payment) still call the fake gRPC servers (tests/shop/run_shop_unit.py)? in a block, chain value is a taskpartly fixed (stage 5 waves 1-2)
8policy blocks, @ownerwritten as in V.5; service and actor policies are compiled (D-5010, D-5523); the @owner row filter is applied to every query of a User principal without a bypass role (D-5030; conformance §13.4 owner-filters-rows-of-other-users, D-7190)—resolved
9fields of pub(api) data written without pubpub(api) on every field used by another modulefields are private by default (§4.2): erratum of V.5erratum
10THUMB_SIZES: [u32; 2] = [256, 1024], PhotoId/Name… in modelTHUMB_SIZES: List<u32> = [64, 256]; PhotoId lives in photossmallkept
11provide Db = Db.open(App.get().dbUrl); BlobStore; provide Client<Recs>Db.open(…)!; ActorRef<Blobs> (local actor) instead of BlobStore; profile dev sets dbUrl = "sqlite::memory:" for unit tests (the cluster runs with APP_DB_URL=builtin://shop)no BlobStore; dev is the default profilekept
12cluster block expose 443 tls(acme: …), seeds k8s(…)written as in V.5 and overridden by flags / env in the acceptancenot applicable on one machineenvironment
13no test ab in V.5 (§18.5 names "search v2" only)ERRATUM V.5 (customer answer 1a, D-6866), now also in the Standard: Shop.search(q) -> List<str>, src/search/search.oxl (variants searchByName / searchByTag, chosen by ab.variant("search v2"), user metric search_found), tests/search_ab_test.oxl (test ab "search v2", guard latency.p95 < SEARCH_P95_LIMIT), traffic/search.oxlrec (400 requests of 200 users, synthesized with scripts/oxlrec.py synth in the format oxl trace --record writes); runs with oxl test examples/shop --kind aberratumerratum

Not done at all: oxl.lock, --inspect-ui of ./shop --role api, oxl build --image (OCI), k8s/ manifests, oxl debug.

Source: examples/shop/DEVIATIONS.md