Repository navigation
fix: the datagram gateway is its own application, release and image - #514
Merged
Merged
Conversation
Closes #513. The two-role split was a switch read inside `asobi_app:start/2`, and that is too late to isolate anything. OTP starts an application's dependencies before its start callback runs, so by the time `ASOBI_ROLE` was read the container parsing packets from the internet had already opened a pool with the tenant credentials (`kura`), run schema migrations and started job workers (`shigoto`), and bound an HTTP port (`nova`). No code inside `start/2` can undo any of it. Only a release that never listed those applications can. So the gateway is one now. `asobi_dgram_gw` is an OTP application holding the thirteen modules that face the internet plus the codec both roles share, and its dependency list is kernel, stdlib, crypto, telemetry and the rate limiter. `asobi` depends on it, never the reverse. Two relx releases follow, and `docker build --target gateway` produces an image whose whole lib directory is asobi_dgram_gw, crypto, kernel, sasl, seki, stdlib, telemetry. Verified by booting it: the application starts standalone, kura, nova and shigoto are not merely unstarted but not loaded, the full supervision tree comes up, and the readiness canary round-trips a real datagram through the receive loop with no engine anywhere. The split needed three things beyond moving files. The gateway's telemetry moved with it - `asobi_dgram_telemetry` owns every `[asobi, dgram, *]` event, because a module in the `asobi` application is not reachable from a release that does not contain it; `asobi_telemetry:events/0` folds that list in, so an operator still attaches to one list and every event still has one definition. It needed a log formatter, because `nova_jsonlogger` lives inside nova and pulling nova in for a formatter would bring cowboy, ranch and erlydtl with it; `asobi_dgram_gw_logger` emits the same JSON-per-line contract with the fields the observability guide documents. And it needed its own sys.config, four lines long, because a gateway container must not be handed ${ASOBI_DB_*} just to render a file it never reads. `ASOBI_ROLE=dgram_gw` on the engine image still starts the gateway and only the gateway, for deployments already using it - and still carries the flaw above, which the guides now say plainly rather than claiming an isolation the code could not provide. Note for whoever publishes images: this adds a second target. The guides name it `ghcr.io/widgrensit/asobi-dgram`; nothing in this repository publishes either image, so that tag needs building wherever `ghcr.io/widgrensit/asobi` is built. `rebar3 as prod release` now requires `-n asobi` or `-n asobi_gw`, which relx enforces once a project declares more than one release.
🟡 Code Coverage — 76.2%8591 of 11280 lines covered. 🟡 ELP Lint — 192 warnings210 diagnostics found. See job logs for details. |
The gateway release had no CI behind it: the reusable workflow builds no releases, so nothing checked that the closure this split exists for stays what it is, and the image the guides name did not get built at all. Two jobs, and the guard is the point of both. `gateway-release` in CI assembles both releases on every PR and asserts the gateway's lib directory: asobi_dgram_gw, crypto, kernel, sasl, seki, stdlib, telemetry, and none of nova, kura, kura_postgres, shigoto, luerl, nova_auth, nova_resilience, cowboy or ranch. That property belongs to relx's output rather than to any module, so a unit test cannot hold it - the closest a test can get is the gateway app's declared dependency list, which is necessary and not sufficient. Assembling the engine release alongside keeps the other half of the spec honest for the same two minutes. Verified both ways: it passes on the gateway release and fails on the engine one. `docker-publish-gateway.yml` builds the image locally, runs the same closure check inside it, and only then pushes. The engine image is deliberately not built here - it comes from asobi_bundle, because it has to carry the full extension closure (#471). The gateway's requirement is the exact inverse, so the bundle is the wrong source for it and this repository is the only place that defines what belongs in it. That contrast is now in the self-hosting guide next to the existing provenance note. Tags follow the retired workflow's scheme, including the {{major}} tag and the reason it is emitted rather than left orphaned.
Taure
added a commit
that referenced
this pull request
Aug 19, 2026
Three names for one artifact, which is two too many: the OTP application is `asobi_dgram_gw`, the release was `asobi_gw` and the image was `asobi-dgram`. The hyphen is also wrong twice over - Erlang names use underscores, and the org's published images use them too. The release and the image are now both `asobi_dgram`, so a self-hoster reads one name in the compose file, the Dockerfile and the release directory. The OTP application keeps `_gw`. Inside it, `asobi_dgram` is already the codec module that both roles share, and the application is specifically the gateway half - so the suffix is carrying meaning there rather than noise. `ghcr.io/widgrensit/asobi-dgram` was published once, from the #514 merge. It is orphaned now and worth deleting: an image nobody should pull is worse sitting in the registry than absent from it.
Taure
added a commit
that referenced
this pull request
Aug 19, 2026
Three names for one artifact, which is two too many: the OTP application is `asobi_dgram_gw`, the release was `asobi_gw` and the image was `asobi-dgram`. The hyphen is also wrong twice over - Erlang names use underscores, and the org's published images use them too. The release and the image are now both `asobi_dgram`, so a self-hoster reads one name in the compose file, the Dockerfile and the release directory. The OTP application keeps `_gw`. Inside it, `asobi_dgram` is already the codec module that both roles share, and the application is specifically the gateway half - so the suffix is carrying meaning there rather than noise. `ghcr.io/widgrensit/asobi-dgram` was published once, from the #514 merge. It is orphaned now and worth deleting: an image nobody should pull is worse sitting in the registry than absent from it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #513.
Why a role could never do this
The two-role split was a switch read inside
asobi_app:start/2. OTP starts an application's dependencies before its start callback runs, so by the timeASOBI_ROLEwas read, the container parsing packets from the internet had already:kura, viakura_app:start/2→ensure_pools())shigoto)nova)None of that can be undone from inside
start/2. Only a release that never listed those applications can avoid it.What this is
asobi_dgram_gwis an OTP application holding the thirteen modules that face the internet plus the codec both roles share. Its dependency list iskernel, stdlib, crypto, telemetry, seki.asobidepends on it; the reverse dependency does not exist, which is what lets the gateway ship without the engine.Two relx releases follow, and
docker build --target gatewayproduces an image whose entire lib directory is:Verified by booting it, not by reading the release spec:
That last line is the one that matters: the readiness canary sends itself a real datagram and waits for it to come back, so the receive loop, the sender, the shards and the binding table all work with no engine anywhere.
Three things the split needed beyond moving files
Telemetry.
asobi_dgram_telemetry(in the new app) owns every[asobi, dgram, *]event, because a module in theasobiapplication is not reachable from a release that does not contain it.asobi_telemetry:events/0folds that list in, so an operator still attaches to one list and every event still has exactly one definition —link_upis emitted by both roles and defined once. The coverage test now derives the emitted set from both modules, and the drift script scans both trees.A log formatter.
nova_jsonloggerlives inside the nova application; pulling nova in for a formatter would bring cowboy, ranch, erlydtl and routing_tree with it and undo the whole thing.asobi_dgram_gw_loggeris the same contract in the smallest form that honours it — one JSON object per line, carrying the fields the observability guide tells operators to query on, encoded with OTP'sjson. It is total on report contents, because a formatter that raises takes the handler down and the node goes quiet.Its own sys.config. Four lines. A gateway container must not be handed
${ASOBI_DB_*}just to render a config file it never reads.Compatibility
ASOBI_ROLE=dgram_gwon the engine image still starts the gateway and only the gateway. It still carries the flaw above; the guides now say so plainly instead of claiming an isolation the code could not provide.rebar3 as prod releasenow needs-n asobior-n asobi_gw— relx refuses to guess once a project declares more than one release. The Dockerfile is updated; the only other mention in the repo is a snippet about a consumer's own release.{asobi, [{dgram, ...}]}configuration keys are unchanged, deliberately.asobi_dgram_envmoved into the gateway app but still reads and writes theasobiapplication's env, because those keys are the documented surface and every existingsys.configspells them that way. ELP flags the cross-app read (W0011); it is the same patternasobi_lua_envalready has, and the moduledoc explains it.For whoever publishes images
This adds a second build target. The guides name it
ghcr.io/widgrensit/asobi-dgram. Nothing in this repository publishes either image, so that tag needs building whereverghcr.io/widgrensit/asobiis built — until it exists, self-hosters can build it themselves withdocker build --target gateway, which the guides say.Checks
fmt --check/xref/dialyzer/elp eqwalize(new modules clean; the 2 inasobi_supand 1 inasobi_telemetryare pre-existing) /elp lint/eunit2147, 0 failures /ct388, 0 failures /ex_doc(2 pre-existing warnings) / all three drift scripts.