10 Commits

Author SHA1 Message Date
f38dec2ac4 Merge pull request 'ci(image): clean up after the image build and the deploy' (#50) from chore/ci-self-cleanup into main
All checks were successful
Build and publish the image / build (push) Successful in 1m37s
Build and publish the image / deploy (push) Successful in 10s
PR checks / checks (push) Successful in 2m42s
Reviewed-on: #50
2026-11-04 12:10:59 +00:00
080f9827b8 ci(image): clean up after the image build and the deploy
All checks were successful
PR checks / checks (pull_request) Successful in 3m41s
The build job runs docker on the runner host's daemon, so every run left
two tagged images behind for good. Remove this run's tags once they are
pushed and cap BuildKit's cache at 5 GB, always, never failing the job.

The deploy drops the dangling image its pull replaced on the rgcom host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-11-02 11:20:50 -06:00
7ac19a791c Merge pull request 'fix(app): turn the APK download back on for v0.7.0' (#49) from fix/apk-serviceable into main
Some checks failed
Build and publish the image / build (push) Successful in 3m55s
Build and publish the image / deploy (push) Successful in 13s
PR checks / checks (push) Failing after 14m24s
Reviewed-on: #49
2026-10-31 05:38:36 +00:00
b8306dcdaf fix(app): turn the APK download back on for v0.7.0
All checks were successful
PR checks / checks (pull_request) Successful in 2m30s
platform.json's androidApk.serviceable goes to true. It was false because
v0.5.0's build did not work. v0.7.0's released APK (SHA256 matches
SHA256SUMS) was installed on an Android 16 emulator, connected to the
public demo and read its Rust servers, so /app/ links the download again.

The comment beside the flag now says when it was last checked and to set
it back to false when `asset` moves to a release nobody has run. PLAN.md
D29 gets a dated note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-31 00:21:46 -05:00
08a4973bde Merge pull request 'feat(app): quote Android v0.7.0, with report, block and hide and account deletion' (#48) from fix/android-v0.7.0 into main
All checks were successful
PR checks / checks (push) Successful in 2m39s
Build and publish the image / build (push) Successful in 4m12s
Build and publish the image / deploy (push) Successful in 37s
Reviewed-on: #48
2026-10-30 23:09:29 +00:00
8b8896f54b feat(app): quote Android v0.7.0, with report, block and hide and account deletion
Some checks failed
PR checks / checks (pull_request) Failing after 3m28s
Android-app v0.7.0 released 2026-10-30 (M8's cutover). platform.json
quotes it and its APK; /app/ gains the two things it added for a member
(report/block/hide on chat lines, and deleting the account), and the
Moderation docs name the app's long press beside the site's menu.

androidApk.serviceable is left false: it records a person having run
the build, and nobody has run v0.7.0's release APK on a phone yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 18:08:14 -05:00
e5ec81b172 Merge pull request 'fix(facts): quote 2026-10-30's releases, and the moderation that 1.21.0 brought' (#47) from fix/platform-facts-2026-10-30 into main
All checks were successful
PR checks / checks (push) Successful in 4m4s
Build and publish the image / build (push) Successful in 5m1s
Build and publish the image / deploy (push) Successful in 1m7s
Reviewed-on: #47
2026-10-30 22:31:46 +00:00
e0b2885cd7 fix(facts): quote 2026-10-30's releases, and the moderation that 1.21.0 brought
All checks were successful
PR checks / checks (pull_request) Successful in 5m26s
check:facts went red on seven facts after the day's releases: Module API
1.21.0, Module-Rust v0.7.0 (coreApi ^1.21.0), Module-uo v1.4.1,
Rust-Plugins v0.3.1, and the Rust bundle 2026.10.30 (plugin v0.3.1).
platform.json now quotes them, verified 2026-10-30.

Re-reading the pages that quote them: Module API 1.21.0 is report, block
and hide, so the Moderation page's Reports section described a queue
that no longer exists (forum-only, and "handling a report never touches
the content"). It now covers chat-line reports, the report snapshot,
Hide for everyone from the queue, and what members do for themselves
(Hide for me, Block, Account -> Blocked). The Rust docs' Feed row names
the chat-line menu. The app's menu is not mentioned: the app release
that carries it (v0.7.0) has not shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 17:30:39 -05:00
c1b38081f5 Merge pull request 'fix(legal): declare report, block and hide in the app's data safety' (#46) from fix/play-data-safety-user-safety into main
Some checks failed
Build and publish the image / build (push) Successful in 1m41s
Build and publish the image / deploy (push) Successful in 10s
PR checks / checks (push) Failing after 3m5s
Reviewed-on: #46
2026-10-30 16:05:05 +00:00
dff4232491 fix(legal): declare report, block and hide in the app's data safety
Some checks failed
PR checks / checks (pull_request) Failing after 3m21s
The app now sends reports, blocks and hides (USER_SAFETY.md step 4,
Android-app#64). A new app row, app-safety, declares them: App activity ->
Other user-generated content (the report's note) and Other actions (the
report, block and hide themselves), collected, not shared, required. Other
actions moves from "not collected" to collected in PLAY_DATA_SAFETY.md.

The deployment row for posted content now says a report keeps a copy of
what it reported, and that blocks and hides are kept on the account and
deleted with it (user_blocks and user_hidden_items cascade).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 11:02:56 -05:00
8 changed files with 152 additions and 26 deletions

View File

@@ -157,6 +157,21 @@ jobs:
docker push "${IMAGE}:latest" docker push "${IMAGE}:latest"
docker push "${IMAGE}:${TAG}" docker push "${IMAGE}:${TAG}"
- name: Remove this run's image and cap the build cache
# The build runs on the HOST daemon, so whatever it leaves stays on the
# runner host for good: every run used to leave two tagged images, and
# each newer `latest` turned the previous one into a permanent `sha-` image
# until the host ran out of disk. Both tags are in the registry by now, so
# the local copies are dead weight. BuildKit keeps its layer cache apart
# from the images, so the next build stays fast; the cache is pruned to a
# cap instead of growing forever (`--reserved-space` is the same flag's
# name on newer Docker). Runs even after a failure, and never fails the job.
if: always()
run: |
docker image rm "${IMAGE}:latest" "${IMAGE}:${TAG}" 2>/dev/null || true
docker builder prune -f --keep-storage 5GB \
|| docker builder prune -f --reserved-space 5GB || true
- name: Log out - name: Log out
if: always() if: always()
run: docker logout "${REGISTRY}" || true run: docker logout "${REGISTRY}" || true
@@ -207,3 +222,11 @@ jobs:
echo "::error::The site did not become healthy within 150s." echo "::error::The site did not become healthy within 150s."
docker compose logs --tail 100 site docker compose logs --tail 100 site
exit 1 exit 1
- name: Remove the image this deploy replaced
# The pull moves `latest` to the new image and leaves the old one untagged
# on the host. Untagged images are never used again (a rollback pulls a
# `sha-` tag from the registry), so drop them. Dangling images only: nothing
# tagged and nothing a container is using is touched.
if: always()
run: docker image prune -f || true

View File

@@ -956,6 +956,8 @@ deliberately so — no fetch can tell whether an APK runs. `checkFacts.mjs` asse
still exist and that `minSdk` still says what "Android 10 or newer" claims, so the link is correct still exist and that `minSdk` still says what "Android 10 or newer" claims, so the link is correct
the moment a working build flips the boolean. The panel is not removed while the link is off: a page the moment a working build flips the boolean. The panel is not removed while the link is off: a page
that omitted sideloading would read, to somebody who knows the APK exists, as a page hiding it. that omitted sideloading would read, to somebody who knows the APK exists, as a page hiding it.
*(2026-10-31: the link is back on. Android `v0.7.0`'s APK was installed on an Android 16 emulator,
connected to the public demo and read its Rust servers, so `serviceable` is `true`.)*
**Three mechanisms this phase added that the plan did not anticipate.** **Three mechanisms this phase added that the plan did not anticipate.**

View File

@@ -38,7 +38,8 @@ Play still counts what the app sends to that server as **collected**: the data l
| Messages | Other in-app messages | Yes | No | Yes | App functionality | Your name, email address and message, if you use Contact; What you post and send from the app | | Messages | Other in-app messages | Yes | No | Yes | App functionality | Your name, email address and message, if you use Contact; What you post and send from the app |
| App activity | App interactions | Yes | No | Yes | App functionality; Personalization | Which notifications you have read, and which you want | | App activity | App interactions | Yes | No | Yes | App functionality; Personalization | Which notifications you have read, and which you want |
| App activity | In-app search history | Yes | No | Yes | App functionality | What you search for | | App activity | In-app search history | Yes | No | Yes | App functionality | What you search for |
| App activity | Other user-generated content | Yes | No | Yes | App functionality | What you post and send from the app | | App activity | Other user-generated content | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance; Personalization | What you post and send from the app; What you report, block or hide |
| App activity | Other actions | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance; Personalization | What you report, block or hide |
| App info and performance | Diagnostics | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance | The app’s version and your Android version | | App info and performance | Diagnostics | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance | The app’s version and your Android version |
| Device or other IDs | Device or other IDs | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance; Account management | The trusted-device token and the device’s name, if you asked for one; Push registration, if you turn notifications on | | Device or other IDs | Device or other IDs | Yes | No | Yes | App functionality; Fraud prevention, security, and compliance; Account management | The trusted-device token and the device’s name, if you asked for one; Push registration, if you turn notifications on |
@@ -56,7 +57,7 @@ The form lists every type, so every one is answered. Vocabulary read from Play C
- **Files and docs:** none collected. - **Files and docs:** none collected.
- **Calendar:** none collected. - **Calendar:** none collected.
- **Contacts:** none collected. - **Contacts:** none collected.
- **App activity:** Installed apps, Other actions: not collected. - **App activity:** Installed apps: not collected.
- **Web browsing:** none collected. - **Web browsing:** none collected.
- **App info and performance:** Crash logs, Other app performance data: not collected. - **App info and performance:** Crash logs, Other app performance data: not collected.
@@ -169,6 +170,19 @@ Most of the app is reading. What it sends is what you write, and most of that is
- **Retention:** Held by the deployment, under its operator’s policy - **Retention:** Held by the deployment, under its operator’s policy
- **Read from:** `data/api/AdminApi.kt` - **Read from:** `data/api/AdminApi.kt`
### What you report, block or hide
**App activity → Other user-generated content; App activity → Other actions.** Collected, not shared, required.
Purposes: App functionality; Fraud prevention, security, and compliance; Personalization.
Long-pressing a chat line offers Report, Hide for me and Block player. A report sends which line it is, the reason you picked and, if you wrote one, a note of up to 500 characters, so the deployment’s staff can review it. Hide for me and Block player send which line it is; the deployment works out who wrote it and keeps the hide or the block on your account, so it applies on the website and on every device. Your list of blocks, and unblocking someone, come from and go to the same place. For staff, Hide for everyone and Show to everyone again send the line too. Nothing is sent until you choose one.
- **Why that answer:** The report’s note is text the user writes, and reporting, blocking and hiding are actions the app sends to the deployment, which keeps them. Reports exist so staff can act on abuse; blocks and hides change what this one reader is shown.
- **Retention:** Held by the deployment, under its operator’s policy
- **In detail:** A block or a hide lasts until you remove it or delete the account, which deletes them. A report stays as a moderation record, with a copy of what the line said, until the operator removes it.
- **Read from:** `data/api/SafetyApi.kt, data/api/dto/SafetyDto.kt, data/api/RustApi.kt`
### Which notifications you have read, and which you want ### Which notifications you have read, and which you want
**App activity → App interactions.** Collected, not shared, required. **App activity → App interactions.** Collected, not shared, required.

View File

@@ -1,6 +1,6 @@
--- ---
title: Moderation title: Moderation
description: Three screens that do three different jobs — Discord moderation, content reports, and appeals against a sanction. description: Three screens that do three different jobs — Discord moderation, content reports, and appeals against a sanction — and what members can do for themselves.
--- ---
import { Aside } from '@astrojs/starlight/components'; import { Aside } from '@astrojs/starlight/components';
@@ -11,7 +11,7 @@ not overlap.
| Screen | Is about | Comes from | | Screen | Is about | Comes from |
|---|---|---| |---|---|---|
| **Moderation** | Your **Discord** server — bans, kicks, mutes, warnings, joins, leaves, filter and spam hits | the bot, captured live | | **Moderation** | Your **Discord** server — bans, kicks, mutes, warnings, joins, leaves, filter and spam hits | the bot, captured live |
| **Reports** | **Team forum content** members have reported | the site | | **Reports** | **Content** members have reported — Team forum threads and posts, and a game's chat lines where its module offers them | the site |
| **Appeals** | Sanctions people are asking you to reverse | the site | | **Appeals** | Sanctions people are asking you to reverse | the site |
## Moderation (Discord) ## Moderation (Discord)
@@ -29,13 +29,20 @@ browsing the window.
## Reports ## Reports
Reports raised by members about Team forum content. Two design decisions show through in Reports raised by members about Team forum threads and posts and, where a game module offers
how this screen behaves: it, the game's chat lines — the Rust module does, from the `⋯` menu on a chat line on the
site and a long press in the [app](/app/). Each report keeps a copy of what the item said when it was
reported, so an author editing it afterwards does not change what staff read. Three design
decisions show through in how this screen behaves:
- **They come to site staff, and a Team's own leaders never see them.** A leader moderates - **They come to site staff, and a Team's own leaders never see them.** A leader moderates
their own forum, so a report *about a leader* has to reach someone above them. their own forum, so a report *about a leader* has to reach someone above them.
- **Handling a report records a decision about the report.** It does not touch the content: - **A report changes nothing on its own.** Filing one must never be a way to hide
hiding or removing a post is done in the forum, or as a sanction against the account. something, so the reported item stays where it is until staff act.
- **Acting is a separate, deliberate step.** *Hide for everyone* on a report hides the item
from everybody except staff, who still see it marked *Hidden by staff* and can show it
again, and it records the report as actioned. Removing content outright, or sanctioning the
account, is still done in the forum or against the account.
The filters are *Open*, *Reviewing*, *Actioned*, *Dismissed* and *All*, and the count of The filters are *Open*, *Reviewing*, *Actioned*, *Dismissed* and *All*, and the count of
open reports sits at the top so the screen is glanceable. open reports sits at the top so the screen is glanceable.
@@ -46,6 +53,19 @@ that never empties stops being read, and the reporter's next report is the one t
matters. matters.
</Aside> </Aside>
## What members do for themselves
Two tools need no staff at all, and nothing about them reaches the queue:
- **Hide for me** takes one item out of that member's own view, on every device.
- **Block** takes everything a person writes — chat lines and forum content — out of the
blocker's view, on the site and in the app. The blocked person is not told. Staff cannot be
blocked, and a block whose target later becomes staff stops applying and says so. A game
player who has not linked a site account is blocked by their game identity, which turns
into a block on their account if they link one later.
A member undoes either from **Account → Blocked**.
## Appeals ## Appeals
An appeal is a request to reverse a sanction, filtered by *Open*, *Pending*, *Under An appeal is a request to reverse a sanction, filtered by *Open*, *Pending*, *Under

View File

@@ -42,7 +42,7 @@ with six tabs.
| Tab | What it shows | | Tab | What it shows |
|---|---| |---|---|
| **Feed** | Kills, chat, comings and goings, the server's own story, RunicNPC bosses, and Rust's world events — cargo, helicopters, airdrops — as they happen | | **Feed** | Kills, chat, comings and goings, the server's own story, RunicNPC bosses, and Rust's world events — cargo, helicopters, airdrops — as they happen. A signed-in member can report a chat line, hide it for themself or block the player from its `⋯` menu, and staff can hide a line for everyone ([Moderation](/docs/administration/moderation/)) |
| **Leaderboard** | Per wipe: kills, deaths, NPC kills, structures, time played, and the chat titles players have earned | | **Leaderboard** | Per wipe: kills, deaths, NPC kills, structures, time played, and the chat titles players have earned |
| **Online** | Who is on right now | | **Online** | Who is on right now |
| **Map** | The [live map](/docs/rust/the-live-map/): the server's own map picture, with layers | | **Map** | The [live map](/docs/rust/the-live-map/): the server's own map picture, with layers |

View File

@@ -128,6 +128,22 @@ export const appFeatures = [
'Visible to the account they belong to and to nobody else.', 'Visible to the account they belong to and to nobody else.',
gate: 'A game module', gate: 'A game module',
}, },
{
title: 'Report, block and hide',
body:
'Long-press a chat line to report it to the site’s staff, hide it for yourself, or ' +
'block the player. A block is kept on your account, so it applies on the website and ' +
'on every device too; staff cannot be blocked, and Account → Blocked undoes any of ' +
'it. Staff get Hide for everyone on the same menu.',
gate: 'A game module whose chat lines can be reported (Rust)',
},
{
title: 'Deleting your account',
body:
'From My account, with your password and, where the account has one, a two-factor ' +
'code. The account is deleted on the deployment at once — see what that removes and ' +
'what it keeps on the delete-account page.',
},
], ],
}, },
{ {

View File

@@ -499,6 +499,51 @@ export const collected = [
'We have no copy, no access and no way to obtain one.', 'We have no copy, no access and no way to obtain one.',
}, },
}, },
/*
* Report, block and hide (USER_SAFETY.md §4.3), on Android-app#64 rather than the edge
* commit the section header names. Read out of `data/api/SafetyApi.kt` and
* `data/api/dto/SafetyDto.kt`: a report sends the item's type and id, a reason and an
* optional note; a block and a hide send the item's type and id; unblocking sends the
* block's id. No request names the person being blocked — the deployment works out who
* wrote the line — so none of it is a User ID.
*/
{
id: 'app-safety',
scope: 'app',
title: 'What you report, block or hide',
body:
'Long-pressing a chat line offers Report, Hide for me and Block player. A report ' +
'sends which line it is, the reason you picked and, if you wrote one, a note of up to ' +
'500 characters, so the deployment’s staff can review it. Hide for me and Block ' +
'player send which line it is; the deployment works out who wrote it and keeps the ' +
'hide or the block on your account, so it applies on the website and on every device. ' +
'Your list of blocks, and unblocking someone, come from and go to the same place. For ' +
'staff, Hide for everyone and Show to everyone again send the line too. Nothing is ' +
'sent until you choose one.',
retention: {
summary: 'Held by the deployment, under its operator’s policy',
detail:
'A block or a hide lasts until you remove it or delete the account, which deletes ' +
'them. A report stays as a moderation record, with a copy of what the line said, ' +
'until the operator removes it.',
},
source: 'data/api/SafetyApi.kt, data/api/dto/SafetyDto.kt, data/api/RustApi.kt',
play: {
types: [
{ category: 'App activity', type: 'Other user-generated content' },
{ category: 'App activity', type: 'Other actions' },
],
collected: true,
shared: false,
required: true,
purposes: ['App functionality', 'Fraud prevention, security, and compliance', 'Personalization'],
answer: 'Collected, not shared, required.',
because:
'The report’s note is text the user writes, and reporting, blocking and hiding are ' +
'actions the app sends to the deployment, which keeps them. Reports exist so staff ' +
'can act on abuse; blocks and hides change what this one reader is shown.',
},
},
{ {
id: 'app-interactions', id: 'app-interactions',
scope: 'app', scope: 'app',
@@ -746,7 +791,10 @@ export const collected = [
title: 'Everything posted on the site', title: 'Everything posted on the site',
body: body:
'Forum threads and replies, uploads, wiki revisions, moderation actions, warnings, ' + 'Forum threads and replies, uploads, wiki revisions, moderation actions, warnings, ' +
'reports and appeals — with the account that made each one. Where the operator has ' + 'reports and appeals — with the account that made each one. A report keeps a copy ' +
'of what the reported post or chat line said, so staff read what was reported even ' +
'after it is edited or pruned. Each member’s blocks and the items they hid for ' +
'themselves are kept against their account, and deleted with it. Where the operator has ' +
'connected Discord, some of that crosses into Discord and is then also subject to ' + 'connected Discord, some of that crosses into Discord and is then also subject to ' +
'Discord’s own terms.', 'Discord’s own terms.',
retention: { retention: {
@@ -755,7 +803,9 @@ export const collected = [
'Soft-deleted forum uploads and Team activity have retention windows an admin ' + 'Soft-deleted forum uploads and Team activity have retention windows an admin ' +
'sets; most other content is kept until somebody removes it.', 'sets; most other content is kept until somebody removes it.',
}, },
source: 'website server/db/schema.sql — team_forum_*, mod_actions, content_reports', source:
'website server/db/schema.sql — team_forum_*, mod_actions, content_reports, ' +
'user_blocks, user_hidden_items',
}, },
{ {
id: 'deploy-engagement', id: 'deploy-engagement',

View File

@@ -12,14 +12,14 @@
"wrong protocol number in the first place." "wrong protocol number in the first place."
], ],
"verifiedOn": "2026-10-29", "verifiedOn": "2026-10-30",
"$comment_moduleApi": [ "$comment_moduleApi": [
"The one version every deployment shares, whichever game it runs. D66: it is the only", "The one version every deployment shares, whichever game it runs. D66: it is the only",
"version the homepage hero and the footer show — a protocol and a bundle belong to a game,", "version the homepage hero and the footer show — a protocol and a bundle belong to a game,",
"so they appear on that game's own pages." "so they appear on that game's own pages."
], ],
"moduleApi": "1.20.0", "moduleApi": "1.21.0",
"$comment_releases": [ "$comment_releases": [
"Every repository whose release the site quotes, flat, so checkFacts.mjs reads them all", "Every repository whose release the site quotes, flat, so checkFacts.mjs reads them all",
@@ -27,12 +27,12 @@
], ],
"releases": { "releases": {
"installer": "v0.8.1", "installer": "v0.8.1",
"Android-app": "v0.6.0", "Android-app": "v0.7.0",
"link": "v2.3.0", "link": "v2.3.0",
"Module-uo": "v1.4.0", "Module-uo": "v1.4.1",
"Rust-Link": "v0.3.0", "Rust-Link": "v0.3.0",
"Rust-Plugins": "v0.3.0", "Rust-Plugins": "v0.3.1",
"Module-Rust": "v0.6.3", "Module-Rust": "v0.7.0",
"runicnpc-rust": "v1.0.0" "runicnpc-rust": "v1.0.0"
}, },
@@ -79,15 +79,15 @@
"protocol": 14, "protocol": 14,
"module": { "module": {
"repo": "Module-Rust", "repo": "Module-Rust",
"coreApi": "^1.19.0", "coreApi": "^1.21.0",
"capabilities": ["rust", "servers", "killfeed", "leaderboard", "presence", "wipes", "identity", "map"] "capabilities": ["rust", "servers", "killfeed", "leaderboard", "presence", "wipes", "identity", "map"]
}, },
"sidecarRepo": "Rust-Link", "sidecarRepo": "Rust-Link",
"pluginRepo": "Rust-Plugins", "pluginRepo": "Rust-Plugins",
"bundle": { "bundle": {
"tag": "2026.10.22", "tag": "2026.10.30",
"sidecar": "v0.3.0", "sidecar": "v0.3.0",
"plugin": "v0.3.0", "plugin": "v0.3.1",
"oxideMin": "2.0.7585", "oxideMin": "2.0.7585",
"carbonMin": "2.0.259", "carbonMin": "2.0.259",
"requiresPlugins": ["Kits", "ZoneManager"], "requiresPlugins": ["Kits", "ZoneManager"],
@@ -120,17 +120,18 @@
"", "",
"`serviceable` is the one value here with no authority to check it against, and it is", "`serviceable` is the one value here with no authority to check it against, and it is",
"deliberately manual. It answers a question no API can: does the published build", "deliberately manual. It answers a question no API can: does the published build",
"actually work. The org lead reports v0.5.0's does not, so the download block renders a", "actually work. v0.5.0's did not, and while false the download block renders a 'being",
"'being replaced' state instead of a link, and flipping this to true is the single edit", "replaced' state instead of a link. A check cannot run an APK; a person can, and this",
"that turns the link back on once a working build is released. A check cannot run an", "is where they record that they did. v0.7.0's was installed on an Android 16 emulator",
"APK; a person can, and this is where they record that they did." "on 2026-10-31, connected to the public demo and read its Rust servers: true.",
"When `asset` moves to a new release, set this back to false until someone has run it."
], ],
"androidApk": { "androidApk": {
"asset": "runic-gateway-0.6.0.apk", "asset": "runic-gateway-0.7.0.apk",
"checksums": "SHA256SUMS", "checksums": "SHA256SUMS",
"minSdk": 29, "minSdk": 29,
"minAndroid": "10", "minAndroid": "10",
"serviceable": false "serviceable": true
}, },
"gitea": { "gitea": {