asobi_ops_extension (asobi v0.72.5)

View Source

The ops plane's one extension seam: /api/v1/ops/ext/:extension/:action.

rpc/0 is player-scoped by construction, so an extension with an operator action - quests.define was the first - had nowhere to put it. ops/0 is that home, and this module is what makes it reachable.

Extensions contribute no routes (ADR 0003). Core owns this one and dispatches every declared action behind it, exactly as it owns one WebSocket frame type and dispatches rpc/0 behind that. The route table stays core's.

What authorises

Nothing here. asobi_ops_auth:verify/1 has already resolved the actor and checked the action's declared class against its caps before this module runs, because asobi_ops_caps:class/2 reads the same manifest entry this dispatch reads. An action nobody declared has no class, and a route with no class is denied - so an unknown extension, an unknown action and a method the action does not answer are all 403 from the security callback rather than 404 from here. That is the same answer the plane gives every other unauthorised call, and it is deliberate: enumerating which extensions are installed is not a capability an unauthorised caller should have.

What is audited

Everything but get. Core wraps the call in asobi_ops_audit:mutation/4 before it runs, so an extension cannot write on this plane without a durable row naming the operator. An extension opts into neither the audit nor its shape; declaring a method other than get is what opts in.

What a handler is given

Module:Function(Params, Ctx), the same shape as an RPC handler:

  • Params is the decoded JSON body for a write, and the parsed query string for a get. Always a map with binary keys.
  • Ctx carries the actor, so a handler can record who asked without another lookup, plus the extension and action it was reached as.

and it returns an asobi_rpc:reply/0: {ok, map()}, {error, Code} or {error, Code, Details}. Codes come from the extension's own codes/0 domain, so the answer is the shared error object every other route already returns.

Summary

Types

What an ops handler is told about its caller.

Types

ctx()

-type ctx() :: #{actor := asobi_ops_auth:actor(), extension := binary(), action := binary()}.

What an ops handler is told about its caller.

Functions

handle/1

-spec handle(cowboy_req:req()) ->
                {json, map()} |
                {json, integer(), map(), map()} |
                {asobi_error, asobi_error:code()} |
                {asobi_error, asobi_error:code(), asobi_error:details()}.