amber-backend/docs/auto-update-contract.md

97 lines
4.5 KiB
Markdown
Raw Permalink Normal View History

# Auto-update — contract (issue #16)
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
The app checks for a newer build and installs it. One variant ships, `clean`;
the server used to pick between that and an `adult` one from the account's
`nsfwEnabled` flag, and no longer does.
Server pieces (this repo):
- `pb_migrations/1786500000_releases.js` — the `releases` collection.
- `pb_hooks/update.pb.js` — the `GET /api/update/manifest` route.
- `scripts/publish-release.sh` — uploads a built artifact as a superuser.
Client pieces (amber-app): `lib/data/update/update_service.dart` (version check +
manifest fetch), the desktop swap-installer, and the Android APK install channel.
## `releases` collection
| Field | Type | Notes |
|---------------|--------|-------|
| `platform` | select | `windows` \| `linux` \| `android`. |
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
| `variant` | select | `clean` \| `adult`. Only `clean` is published now. The field stays because every installed app compares it against its own and refuses a mismatch **silently** — removing it would strand those installs with no error to see. |
| `version` | text | Human semver shown to the user, e.g. `1.0.1`. |
| `buildNumber` | number | Monotonic int; the comparator the updater actually uses. |
| `file` | file | The artifact: windows = `.exe` installer, linux = `.zip` of the release bundle, android = `.apk`. Protected (see rules). |
| `sha256` | text | Lowercase hex SHA-256 of the artifact; verified before install. |
| `size` | number | Bytes. |
| `notes` | text | Optional release notes (shown in the update prompt). |
| `created`/`updated` | autodate | |
Unique index on `(platform, variant, buildNumber)`.
**Access rules.** Read is gated:
`@request.auth.id != '' && (variant = 'clean' || @request.auth.nsfwEnabled = true)`
— any signed-in account reads **clean** rows; only an `nsfwEnabled` account reads
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
**adult** rows. That rule is deliberately left in place: `adult` rows from before
the split are still in this collection, and the rule is what keeps them out of
reach. The `file` field is **`protected: true`**, which is what gates the bytes:
a protected file is served only with a short-lived file token
(`POST /api/files/token`) whose grant **re-checks the view rule above** (and
without the flag the file URL would be public regardless of the view rule). `create`/`update`/`delete`
are **superuser-only** (null rules); publishing goes through the admin API.
## Endpoint
### `GET /api/update/manifest?platform=windows|linux|android` (auth: users)
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
Returns the latest build for the caller's platform, always `variant: "clean"`.
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
- No release for that platform → `{ "available": false }`.
- Otherwise:
```json
{
"available": true,
"platform": "linux",
"variant": "clean",
"version": "1.0.1",
"buildNumber": 2,
"notes": "…",
"sha256": "<64 hex>",
"size": 12345678,
"filename": "amber-linux-clean.zip",
"downloadPath": "/api/files/releases/<recordId>/<filename>"
}
```
**Download.** The app mints a file token (`POST /api/files/token`, its user
auth) and GETs `{base}{downloadPath}?token=<fileToken>`, then verifies `sha256`
before installing.
## Publishing a build
Build the artifact, then (on the build machine):
```bash
PB_ADMIN_TOKEN=<superuser token> ./scripts/publish-release.sh \
--platform linux --variant clean --version 1.0.1 --build 2 \
--file build/amber-linux-clean.zip --notes "What changed"
```
`PB_ADMIN_EMAIL` + `PB_ADMIN_PASSWORD` work instead of a token. The script
computes the SHA-256 + size and uploads via the superuser REST API. Re-publishing
the same `(platform, variant, buildNumber)` is rejected by the unique index —
bump `buildNumber` for each release.
**Windows publishes the installer**, `amber-setup-<variant>-<version>.exe`, not
the zip. One artifact serves both jobs: people download and run it, and the app's
updater runs the same file with `/SILENT`, which is why `DesktopInstaller`
dispatches on the extension. The zip `release_windows.ps1` still builds beside it
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
is for unpacking by hand — do not publish it.
## Gating summary
Stop choosing an update variant, and drop the 18+ controls Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
2026-08-30 02:09:10 +00:00
There is nothing left to gate between: one variant is published, and the manifest
hook returns it to everyone.
`nsfwEnabled` still exists on the `users` record and the collection rules still
read it. Both stay because `adult` rows from before the split are still in the
collection, and those rules are what keeps them unreachable. Nothing writes the
flag any more: the manifest hook ignores it, `whoami` no longer returns it, and
the status dashboard's toggle is gone.