Canonical caller and manifest-level authorization helpers.
New protocols opt into enforcement with
:authorization {:caller :member} on a capability definition. Legacy
capabilities without that key retain their current behavior while they are
migrated deliberately. Actor identity always comes from MoM's top-level
:caller envelope, never from :event/data.
Canonical caller and manifest-level authorization helpers.
New protocols opt into enforcement with
`:authorization {:caller :member}` on a capability definition. Legacy
capabilities without that key retain their current behavior while they are
migrated deliberately. Actor identity always comes from MoM's top-level
`:caller` envelope, never from `:event/data`.Payload-bound command idempotency for pure Game Master transitions.
Payload-bound command idempotency for pure Game Master transitions.
Identity: sign-in, session and seat resolution for a Game Master.
Deliberately NOT under game.protocols. Everything in that package is a
pure event handler with a manifest, dispatched from MoM through
/v1/gm/handle and returning :updated-state. This is the opposite shape: HTTP
routes served to a browser, proxying MoM, holding no game state and
advertising no handlers. It belongs with the runtime concerns (config,
dispatch, heartbeat) rather than the game protocols.
The flow, end to end:
A seat is a game membership owned by the signed-in human, matched by
owner-id or owner-email. Most authenticated reads are seat-scoped, so
require-selected-seat is the usual entry point for a route.
Game-specific behaviour is injected through an options map rather than
baked in — see session-context.
Identity: sign-in, session and seat resolution for a Game Master.
Deliberately NOT under `game.protocols`. Everything in that package is a
pure event handler with a manifest, dispatched from MoM through
/v1/gm/handle and returning :updated-state. This is the opposite shape: HTTP
routes served to a *browser*, proxying MoM, holding no game state and
advertising no handlers. It belongs with the runtime concerns (config,
dispatch, heartbeat) rather than the game protocols.
The flow, end to end:
1. the browser GETs /auth.json from the GM it is talking to
2. it redirects to the hosted auth app with ?config=<that url>
3. the auth app signs the user in and redirects to :redirect_uri
(/auth/callback on this GM)
4. the GM bounces to the app with the token on the query string
5. the browser stores the token and sends it as a Bearer header
A *seat* is a game membership owned by the signed-in human, matched by
owner-id or owner-email. Most authenticated reads are seat-scoped, so
`require-selected-seat` is the usual entry point for a route.
Game-specific behaviour is injected through an options map rather than
baked in — see `session-context`.Idempotent startup seed for the Market taxonomy. Posts register-category and register-hashtag events to MoM as the Game Master so the marketplace UI has a non-empty set of categories and hashtags to render on day one.
Once a real curator process exists this seeder can go away.
Idempotent startup seed for the Market taxonomy. Posts register-category and register-hashtag events to MoM as the Game Master so the marketplace UI has a non-empty set of categories and hashtags to render on day one. Once a real curator process exists this seeder can go away.
Anonymous visitors: the web layer of coseller attribution for readers who have no seat yet.
A reader arriving through a ?ref= link cannot use /events — that route is
seat-gated, and event-payload rewrites the actor to the seat. The GM mints
them a visitor:<uuid> instead and records real touches against it; when
they later sign in, coseller/claim-visitor re-keys those touches onto their
player-name. See game.protocols.coseller for why the prefix is load-bearing.
Deliberately NOT under game.protocols, for the same reason as
game.gm.identity: everything there is a pure event handler with a manifest,
dispatched from MoM through /v1/gm/handle. This is the opposite shape — HTTP
routes served to a browser, proxying MoM, holding no game state and
advertising no handlers. The protocol half of anonymous visitors is already
in game.protocols.coseller and stays there.
A consuming GM supplies three things:
the routes themselves — gm-lib carries no compojure dependency, so each game keeps its own route definitions and delegates here:
(POST "/public/touch" [:as request] (gm-visitor/record-visitor-touch request)) (POST "/coseller/claim-visitor" [:as request] (let [{:keys [context seat response]} (require-selected-seat request)] (or response (gm-visitor/claim-visitor-touches request (:game-name context) seat {:submit-ui-event-fn submit-ui-event}))))
ring.middleware.cookies/wrap-cookies in the app's middleware stack.
Without it :cookies is absent on the request and unserialised on the
response, so every visit mints a new id and nothing is ever claimable
— a silent failure with a 200 on every call. This is the one wiring
mistake that looks like it works.
the dispatch fn for the claim (:submit-ui-event-fn), because the claim
must run as the signed-in caller, and building that payload is
game-specific. The public touch needs no injection: it runs on service
credentials via game.gm.identity/submit-service-event, and the game
name comes from the runtime.
Anonymous visitors: the web layer of coseller attribution for readers who
have no seat yet.
A reader arriving through a `?ref=` link cannot use /events — that route is
seat-gated, and event-payload rewrites the actor to the seat. The GM mints
them a `visitor:<uuid>` instead and records real touches against it; when
they later sign in, `coseller/claim-visitor` re-keys those touches onto their
player-name. See `game.protocols.coseller` for why the prefix is load-bearing.
Deliberately NOT under `game.protocols`, for the same reason as
`game.gm.identity`: everything there is a pure event handler with a manifest,
dispatched from MoM through /v1/gm/handle. This is the opposite shape — HTTP
routes served to a *browser*, proxying MoM, holding no game state and
advertising no handlers. The *protocol* half of anonymous visitors is already
in `game.protocols.coseller` and stays there.
A consuming GM supplies three things:
1. the routes themselves — gm-lib carries no compojure dependency, so each
game keeps its own route definitions and delegates here:
(POST "/public/touch" [:as request]
(gm-visitor/record-visitor-touch request))
(POST "/coseller/claim-visitor" [:as request]
(let [{:keys [context seat response]} (require-selected-seat request)]
(or response
(gm-visitor/claim-visitor-touches
request (:game-name context) seat
{:submit-ui-event-fn submit-ui-event}))))
2. `ring.middleware.cookies/wrap-cookies` in the app's middleware stack.
Without it `:cookies` is absent on the request and unserialised on the
response, so every visit mints a *new* id and nothing is ever claimable
— a silent failure with a 200 on every call. This is the one wiring
mistake that looks like it works.
3. the dispatch fn for the claim (`:submit-ui-event-fn`), because the claim
must run as the signed-in caller, and building that payload is
game-specific. The public touch needs no injection: it runs on service
credentials via `game.gm.identity/submit-service-event`, and the game
name comes from the runtime.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |