Nothing read `users.nsfwEnabled` any more. `/api/amber/whoami` stopped returning
it, `GET /api/update/manifest` always answers `clean`, and the `releases` read
rule stopped mentioning it in migration 1793000000, when the nine 18+ rows (437
MB) were deleted. What was left was a per-account flag nobody writes and nobody
reads, sitting on the family's accounts and showing up in every schema dump and
access-rule review as if it still gated something. A flag that looks like a
protection but protects nothing costs the next reader the work of proving it dead.
One live reader did remain: pb_public/index.html showed a "Zdroj 18+" source
field and wrote `adultAddonUrl` into the encrypted addon config for a flagged
account, and picked a different child-profile label for one. Both are gone, so
the page offers the same three sources to everyone.
Migration 1797000000 drops the field, rewrites users.updateRule to
`id = @request.auth.id` (the clause it loses guarded a field that no longer
exists), and removes `adult` from releases.variant's allowed values. Checked
before writing it: 15 release rows, every one `clean`; three accounts carried the
flag (the owner's, one family member's, the retired test account); the separate
18+ app has no accounts and no PocketBase, so nothing outside Amber reads it.
`variant` itself stays and stays required: every installed Amber compares it
against its own build and refuses a mismatch SILENTLY, so dropping the field
would strand those installs with no error to see. publish-release.sh now accepts
only `clean`.
The status dashboard stops listing `adult-stremio-addon` and `stripchat-proxy`.
Both still run on the VPS; they are the owner's, not Amber's, and Amber never
reached them.
scripts/verify.py asserts the flag stays gone (field absent, no rule mentions it,
updateRule own-record-only). Run against a local PocketBase built from origin/main
it failed on exactly those three checks; with this migration applied, ALL CHECKS
PASSED.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The E2E caught a real leak: a non-nsfw (even anonymous) account could download
an adult artifact. In PocketBase, file protection is a per-FIELD flag, not
derived from the collection view rule — the original migration left releases.file
unprotected, so its URL was public despite the gated read rules.
- Add protected:true to the file field (correct for fresh installs).
- 1786500001_releases_protect_file.js: alter the field on the already-deployed
instance (applied migrations don't re-run, so the fix needs its own migration).
- Doc: correct the gating explanation (protection is the field flag; the file
token grant then re-checks the view rule).
With this, a protected file needs a file token whose grant re-checks the view
rule, so a clean account is denied the adult artifact.
Adds the server side of auto-update (issue #16, was epic #6's [H]).
- pb_migrations/1786500000_releases.js: a superuser-only 'releases' collection
(platform, variant, version, buildNumber, file, sha256, size, notes). Read is
gated in the rules — any signed-in account sees clean builds; only an
nsfwEnabled account sees adult ones — so PocketBase's native protected-file
serving hands adult bytes only to flagged accounts (no custom streaming).
- pb_hooks/update.pb.js: GET /api/update/manifest?platform=… (auth). Picks the
variant server-side from the caller's nsfwEnabled (adult) vs clean — the client
can't request adult — and returns the latest build's version/buildNumber/notes/
sha256/size + the protected downloadPath.
- scripts/publish-release.sh: uploads a built artifact as a superuser (computes
sha256 + size, multipart POST). Token or email+password via env.
- docs/auto-update-contract.md: the collection, endpoint, download flow, gating.
Migration + hook + script syntax-checked. Live verification pends deploying this
to the PB (collection auto-applies on boot, hook loads from pb_hooks/).