Provisioning already created each member's Jellyfin account and signed in as
them to get a token — its own header says the token exists so "the person's
Officer session can already read their library". It was then dropped, because
linkJellyfinAccount had nowhere to put it.
So every non-owner resolved to no config and got a 503 setup form, asking them
to connect a Jellyfin server with a password nobody knows. It reads as a
permissions fault and is not one: the grant, the role and the account list were
all correct.
jellyfin_accounts now carries access_token and device_id, getConfig falls back
to the link row when the caller has no registry of their own, and there is no
path from there to the owner's token — a member resolving to the owner's
credential would read the owner's watch state and every library.
POST /_config/:id/provision repairs accounts linked before the column existed.
It resets the password to mint, which is free on an account Officer created
(random, discarded, known to nobody) and destructive on an adopted one, so
adopted accounts are skipped unless includeAdopted says otherwise.
Applied on alpha: conceiba and guipinto minted and verified against the live
server; fedra and diogo skipped as adopted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A new Jellyfin user can see nothing: EnableAllFolders is false by default, so a provisioned account
signs in to an empty home screen and looks like a bug in Officer rather than an unset flag.
POST /Users/{id}/Policy replaces rather than merges, so the policy Jellyfin returned from /Users/New
goes straight back with one field flipped. A hand-built body would quietly reset the other 43
properties. EnabledFolders is left as-is — ignored while the blanket flag is on, and still there if
the owner ever turns it off.
Not fatal if it fails: the account exists and works, and failing the create here would leave an
orphan upstream with no mapping row, which is worse than a user who cannot see films yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
the platform pushes the account list (users:list, declared by userProvisioning
in the manifest); this decides what to do about each one and does it.
DECIDING IS SEPARATE FROM DOING. reconcile.ts is pure and has no idea what
jellyfin is — which is why it can be tested against the real populations on
alpha without creating anything. nine tests, built on the actual overlap:
diogo and fedra already have jellyfin accounts, conceiba and guipinto do not,
rezio and peter exist only on jellyfin.
that test found a real bug before it shipped. officer's `username` was unique
CASE-SENSITIVELY, so `diogo` and `DIOGO` could both exist here while colliding
on jellyfin's uppercased NormalizedUsername index — and the first version
handed both the same jellyfin account, because it only checked links that
already existed rather than ones claimed during the pass. the platform side is
now case-insensitive too, so the collision cannot be created in the first
place; the guard stays because the constraint and the guard protect different
moments.
three outcomes, and the third is the point: LINK an account that already
exists, CREATE one that does not, LEAVE ALONE a jellyfin account with no
officer user. never delete. an absence in officer is indistinguishable from a
list that has not been told about someone yet, and a wrong deletion costs
somebody their watch history.
`adopted` carries that distinction into the mapping table. false means officer
made the account and may remove it; true means it existed first and was merely
linked.
NOBODY IS EVER TOLD THEIR PASSWORD. jellyfin has no must-change flag — verified
against its openapi, UserPolicy has 44 properties and none of them is one — so
a handed-over password stays valid forever. instead the generated one is used
once, immediately, to mint that person's own access token, and discarded. the
account exists and their officer session can already read their library.
which also makes rule 4 unskippable: the sign-in that verifies the account is
the same call that obtains the token, so there is no version of this that
"verifies later".
one failure does not stop the rest. a partially provisioned server is fixed by
running again; a run that aborts on the first error leaves everyone after them
unprovisioned and no record of who.